summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.gitmodules3
m---------.mason0
-rw-r--r--.travis.yml25
-rw-r--r--Makefile30
-rw-r--r--README.md82
m---------android-ndk0
-rw-r--r--android/LICENSE_COFFEECATCH23
-rw-r--r--android/LICENSE_COMMONS.txt202
-rw-r--r--android/LICENSE_GESTURES.md13
-rw-r--r--android/cpp/coffeecatch.c1215
-rw-r--r--android/cpp/coffeecatch.c.orig1338
-rw-r--r--android/cpp/coffeecatch_r10c.diff216
-rw-r--r--android/cpp/coffeejni.c185
-rw-r--r--android/cpp/jni.cpp987
-rw-r--r--android/cpp/native_map_view.cpp738
-rw-r--r--android/java/.gitignore18
-rw-r--r--android/java/app/build.gradle40
-rw-r--r--android/java/app/lint.xml34
-rw-r--r--android/java/app/src/main/AndroidManifest.xml24
-rw-r--r--android/java/app/src/main/java/com/mapbox/mapboxgl/app/MainActivity.java255
-rw-r--r--android/java/app/src/main/java/com/mapbox/mapboxgl/app/MapFragment.java119
-rw-r--r--android/java/app/src/main/res/drawable-hdpi/ic_action_about.pngbin0 -> 683 bytes
-rw-r--r--android/java/app/src/main/res/drawable-hdpi/ic_action_location_found.pngbin0 -> 722 bytes
-rw-r--r--android/java/app/src/main/res/drawable-hdpi/icon.pngbin0 -> 2546 bytes
-rw-r--r--android/java/app/src/main/res/drawable-hdpi/icon_burned.pngbin0 -> 2546 bytes
-rw-r--r--android/java/app/src/main/res/drawable-mdpi/compass.pngbin0 -> 2376 bytes
-rw-r--r--android/java/app/src/main/res/drawable-mdpi/ic_action_about.pngbin0 -> 465 bytes
-rw-r--r--android/java/app/src/main/res/drawable-mdpi/ic_action_location_found.pngbin0 -> 484 bytes
-rw-r--r--android/java/app/src/main/res/drawable-mdpi/icon.pngbin0 -> 2015 bytes
-rw-r--r--android/java/app/src/main/res/drawable-mdpi/icon_burned.pngbin0 -> 2015 bytes
-rw-r--r--android/java/app/src/main/res/drawable-xhdpi/ic_action_about.pngbin0 -> 860 bytes
-rw-r--r--android/java/app/src/main/res/drawable-xhdpi/ic_action_location_found.pngbin0 -> 994 bytes
-rw-r--r--android/java/app/src/main/res/drawable-xhdpi/icon.pngbin0 -> 3358 bytes
-rw-r--r--android/java/app/src/main/res/drawable-xhdpi/icon_burned.pngbin0 -> 3358 bytes
-rw-r--r--android/java/app/src/main/res/drawable-xxhdpi/ic_action_about.pngbin0 -> 1409 bytes
-rw-r--r--android/java/app/src/main/res/drawable-xxhdpi/ic_action_location_found.pngbin0 -> 1432 bytes
-rw-r--r--android/java/app/src/main/res/drawable-xxhdpi/icon.pngbin0 -> 10233 bytes
-rw-r--r--android/java/app/src/main/res/drawable-xxhdpi/icon_burned.pngbin0 -> 10233 bytes
-rw-r--r--android/java/app/src/main/res/layout/activity_main.xml51
-rw-r--r--android/java/app/src/main/res/layout/fragment_main.xml5
-rw-r--r--android/java/app/src/main/res/menu/menu_main.xml11
-rw-r--r--android/java/app/src/main/res/values/strings.xml21
-rw-r--r--android/java/app/src/main/res/values/styles.xml5
-rw-r--r--android/java/build.gradle15
-rw-r--r--android/java/gradle/wrapper/gradle-wrapper.jarbin0 -> 49896 bytes
-rw-r--r--android/java/gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xandroid/java/gradlew164
-rw-r--r--android/java/gradlew.bat90
-rw-r--r--android/java/lib/build.gradle39
-rw-r--r--android/java/lib/lint.xml3
-rw-r--r--android/java/lib/src/main/AndroidManifest.xml9
-rw-r--r--android/java/lib/src/main/java/com/almeros/android/multitouch/BaseGestureDetector.java150
-rw-r--r--android/java/lib/src/main/java/com/almeros/android/multitouch/MoveGestureDetector.java174
-rw-r--r--android/java/lib/src/main/java/com/almeros/android/multitouch/RotateGestureDetector.java169
-rw-r--r--android/java/lib/src/main/java/com/almeros/android/multitouch/ShoveGestureDetector.java202
-rw-r--r--android/java/lib/src/main/java/com/almeros/android/multitouch/TwoFingerGestureDetector.java179
-rw-r--r--android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java161
-rw-r--r--android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java185
-rw-r--r--android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java180
-rw-r--r--android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java213
-rw-r--r--android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java223
-rw-r--r--android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLat.java87
-rw-r--r--android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLatZoom.java82
-rw-r--r--android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java1212
-rw-r--r--android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java542
-rw-r--r--android/java/lib/src/main/res/values/attrs.xml23
-rw-r--r--android/java/settings.gradle2
-rw-r--r--android/mapboxgl-app.gyp66
-rw-r--r--android/test/.gitignore1
-rw-r--r--android/test/features/load-app.feature5
-rwxr-xr-xandroid/test/features/step_definitions/calabash_steps.rb1
-rw-r--r--android/test/features/step_definitions/my_steps.rb6
-rwxr-xr-xandroid/test/features/support/app_installation_hooks.rb36
-rwxr-xr-xandroid/test/features/support/app_life_cycle_hooks.rb13
-rwxr-xr-xandroid/test/features/support/env.rb1
-rwxr-xr-xandroid/test/features/support/hooks.rb0
-rwxr-xr-xandroid/test/upload_testmunk.sh27
-rwxr-xr-xconfigure25
-rw-r--r--gyp/common.gypi1
-rw-r--r--gyp/mbgl-android.gypi78
-rw-r--r--gyp/mbgl-ios.gypi1
-rw-r--r--gyp/mbgl-linux.gypi1
-rw-r--r--gyp/mbgl-osx.gypi1
-rw-r--r--gyp/mbgl-platform.gypi4
-rw-r--r--include/coffeecatch/coffeecatch.h226
-rw-r--r--include/coffeecatch/coffeejni.h133
-rw-r--r--include/mbgl/android/jni.hpp40
-rw-r--r--include/mbgl/android/native_map_view.hpp86
-rw-r--r--include/mbgl/map/map.hpp29
-rw-r--r--include/mbgl/platform/android/log_android.hpp24
-rw-r--r--include/mbgl/platform/default/headless_view.hpp1
-rw-r--r--include/mbgl/platform/event.hpp8
-rw-r--r--include/mbgl/platform/gl.hpp12
-rw-r--r--include/mbgl/storage/asset_request_baton.hpp38
-rw-r--r--include/mbgl/storage/caching_http_file_source.hpp5
-rw-r--r--include/mbgl/storage/http_request_baton.hpp2
-rw-r--r--include/mbgl/util/uv.hpp2
-rw-r--r--platform/android/asset_request_baton_libzip.cpp93
-rw-r--r--platform/android/cache_database_data.cpp13
-rw-r--r--platform/android/log_android.cpp59
-rw-r--r--platform/darwin/http_request_baton_cocoa.mm4
-rw-r--r--platform/default/asset_request_baton_noop.cpp22
-rw-r--r--platform/default/glfw_view.cpp6
-rw-r--r--platform/default/headless_view.cpp37
-rw-r--r--platform/default/http_request_baton_curl.cpp114
-rw-r--r--platform/default/image.cpp5
-rwxr-xr-xscripts/travis_before_install.sh78
-rwxr-xr-xscripts/travis_helper.sh20
-rwxr-xr-xscripts/travis_script.sh12
-rw-r--r--src/mbgl/map/map.cpp139
-rw-r--r--src/mbgl/platform/gl.cpp4
-rw-r--r--src/mbgl/renderer/painter.cpp6
-rw-r--r--src/mbgl/renderer/painter_prerender.cpp2
-rw-r--r--src/mbgl/renderer/prerendered_texture.cpp96
-rw-r--r--src/mbgl/renderer/prerendered_texture.hpp5
-rw-r--r--src/mbgl/storage/asset_request.cpp37
-rw-r--r--src/mbgl/storage/asset_request.hpp27
-rw-r--r--src/mbgl/storage/asset_request_baton.cpp60
-rw-r--r--src/mbgl/storage/base_request.cpp10
-rw-r--r--src/mbgl/storage/base_request.hpp2
-rw-r--r--src/mbgl/storage/caching_http_file_source.cpp15
-rw-r--r--src/mbgl/storage/file_request.cpp4
-rw-r--r--src/mbgl/storage/file_request.hpp2
-rw-r--r--src/mbgl/storage/file_request_baton.cpp59
-rw-r--r--src/mbgl/storage/file_request_baton.hpp12
-rw-r--r--src/mbgl/storage/http_request.cpp111
-rw-r--r--src/mbgl/storage/http_request.hpp10
-rw-r--r--src/mbgl/storage/http_request_baton.cpp2
-rw-r--r--src/mbgl/style/style.cpp2
-rwxr-xr-xupdate_icons.sh23
131 files changed, 11512 insertions, 204 deletions
diff --git a/.gitignore b/.gitignore
index 38dfa73076..98d9e28281 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/.gitmodules b/.gitmodules
index 588802377e..dc490184f6 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -13,3 +13,6 @@
[submodule "styles"]
path = styles
url = https://github.com/mapbox/mapbox-gl-styles.git
+[submodule "android-ndk"]
+ path = android-ndk
+ url = https://github.com/mapbox/android-ndk.git
diff --git a/.mason b/.mason
-Subproject 462fe28479ea700943abd2fde15b03038a168da
+Subproject a3ebabd0c6d57bbe2687b8a46e25aeff0ca2ab4
diff --git a/.travis.yml b/.travis.yml
index a7c7109f03..bdb2cfd20d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,12 @@ matrix:
- os: linux
include:
- os: linux
+ env: BUILDTYPE=Release JOBS=16 MASON_PLATFORM=android TESTMUNK=yes
+ compiler: clang
+ - os: linux
+ env: BUILDTYPE=Debug JOBS=16 MASON_PLATFORM=android TESTMUNK=no
+ compiler: clang
+ - os: linux
env: BUILDTYPE=Release JOBS=16
compiler: clang
- os: linux
@@ -13,6 +19,18 @@ matrix:
- os: linux
env: BUILDTYPE=Release JOBS=8
compiler: gcc
+ - os: linux
+ env: BUILDTYPE=Debug JOBS=8
+ compiler: gcc
+ - os: osx
+ env: BUILDTYPE=Release JOBS=8 MASON_PLATFORM=android TESTMUNK=no
+ compiler: clang
+ - os: osx
+ env: BUILDTYPE=Debug JOBS=8 MASON_PLATFORM=android TESTMUNK=no
+ compiler: clang
+ - os: osx
+ env: BUILDTYPE=Release JOBS=8
+ compiler: clang
- os: osx
env: BUILDTYPE=Debug JOBS=8
compiler: clang
@@ -22,12 +40,19 @@ env:
- secure: "bG4YYWMfl9API0MSRgmOaJrlGLv06tRg9KJNawBieZvBJbITPpxVGJZT3/l/SEJ+Rl15e2dRex4k+MGQlmT2SqPQxTEYWv1qxNigKPPcla7IWeNmWWqW8uVvFjdglojgBOK2k/xErVQtA4zDfi3mwSXH4DKwquXWsoEKmX2SV7M="
- secure: "Cbvap9ubVKgjPe3hUhI6JGeDZzBXHpOG9RaYKh+SdoIPhKnlJiNOYm1egomi+e4uqJInlFKuVHTw7Ng9Cun6Zm0jIxpkSchv1GpsR7hmB3UGnGed19Dw8121FwuUaktN+4YnbVlsyd+u8EHD3+h58t4eELrLrZolM4rS7DL6caA="
- secure: "RiBIBfVhhaMjU5ksuwJO3shdvG9FpinBjdSv4co9jg9171SR8edNriedHjVKSIeBhSGNmZmX+twS3dJS/By6tl/LKh9sTynA+ZAYYljkE7jn881B/gMrlYvdAA6og5KvkhV1/0iJWlhuZrMTkhpDR200iLgg3EWBhWjltzmDW/I="
+ - secure: "CHBiUM60TolDbQnn+4IRA/tvOKwKs3g9EDvv8YHSJMg3FuHmjKQkprBasvxf3hnTXg4WLZEubmeDcyJ6RRzPP5mMSr/hksYl0pSjj/6TUecE5fHPVVeN7txVqkpOBf9i45Y+iBUQMjBb1NnDK3pHXxpnAs1Q/pe7vReErj4GF1U="
+ - secure: "E1+87Ni3BxqDDTLf3Z2hjmFy0VeFzoLBPLAqchJGCxSblgjKdEbg+18TVM7faD57JI8WQ1jK5Z+p4ebqX0S5TAGlGsnFhcP7+ClntVi/pTkKZrF8ynoH8aIFhZCZ8wqaEVZeljO9ZFrW1VIxWlIqM1U8+WvcGki1NxGkPNaPhpo="
- LD_LIBRARY_PATH: '/usr/local/lib'
+ - TERM=dumb
before_install:
- source ./scripts/local_mason.sh
- source ./scripts/travis_helper.sh
- source ./scripts/flags.sh
+- export ANDROID_NDK_PATH="$(pwd)/android-ndk-r10c"
+- export NDK_HOME="$(pwd)/android-ndk-r10c"
+- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then export JAVA_HOME="$(pwd)/jdk1.7.0_71"; fi
+- export ANDROID_HOME="$(pwd)/android-sdk"
- ./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
diff --git a/Makefile b/Makefile
index 539ab12bcf..8cb88b327d 100644
--- a/Makefile
+++ b/Makefile
@@ -69,6 +69,31 @@ build/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj: ios/mapbox-gl-cocoa/app/ma
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: 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 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 build
+
##### Test cases ###############################################################
test: build/test/Makefile
@@ -131,7 +156,10 @@ clean: 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 3c8fb2fd38..400dea0f78 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,11 @@ implemented in C++11, currently targeting iOS, OS X, and Ubuntu Linux.
We try to link to as many system-provided libraries as possible. When these are unavailable or too outdated, we run a thin build-script layer called [Mason](https://github.com/mapbox/mason) to automate builds, and load precompiled binary packages when possible.
+Be sure to pull down all submodules first:
+
+ git submodule init
+ git submodule update
+
## OS X
On OS X, we are using `zlib`, `SQLite3`, `libcurl` and `libpng` provided by the operating system. In addition to that, you need to have the Boost headers installed. To install all prerequisites, use [Homebrew](http://brew.sh/) and type `brew install pkg-config boost`.
@@ -43,12 +48,7 @@ iOS makes use of a Cocoa-specific API called [`mapbox-gl-cocoa`](https://github.
If you intend to develop here, `mapbox-gl-cocoa` is included as a submodule of the overall build setup.
-To pull down the submodule(s), run:
-
- git submodule init
- git submodule update
-
-Then, you can run `make iproj` to create and open an Xcode project with an iOS-specific view controller housing. This will automatically install required dependencies as well.
+You can run `make iproj` to create and open an Xcode project with an iOS-specific view controller housing. This will automatically install required dependencies as well.
Note that if you are doing OS X development as well, to toggle from OS X back to iOS, you will need to `make iproj` again.
@@ -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,58 @@ 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 pkg-config
+
+Install Oracle JDK 7 (requires license agreement) from http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
+
+Or the OPEN JDK:
+
+ sudo apt-get install -y openjdk-7-jdk
+
+Install Android NDK:
+
+ sudo apt-get install p7zip-full
+ wget http://dl.google.com/android/ndk/android-ndk-r10c-linux-x86_64.bin
+ chmod +x android-ndk-r10c-linux-x86_64.bin
+ 7z x ./android-ndk-r10c-linux-x86_64.bin > /dev/null
+ export ANDROID_NDK_PATH="$(pwd)/android-ndk-r10c"
+ export NDK_PATH=${ANDROID_NDK_PATH}
+
+Install Android SDK:
+
+ sudo apt-get install lib32stdc++6 lib32z1
+ wget http://dl.google.com/android/android-sdk_r23.0.2-linux.tgz
+ tar -xzf ./android-sdk_r23.0.2-linux.tgz
+ ./android-sdk-linux/tools/android update sdk -u -a -t tools,platform-tools,build-tools-21.1.1,android-21,extra-android-m2repository,extra-google-m2repository
+ export ANDROID_HOME="$(pwd)/android-sdk-linux"
+
+Run:
+
+ make android
+
+APKs for testing are output to `android/java/app/build/outputs/apk/app-debug.apk`
+
+You can also open `android/java` in Android Studio.
+
+## Android (on OS X)
+
+Install the [Android NDK](https://developer.android.com/tools/sdk/ndk/index.html) for 64-bit OS X.
+
+ export ANDROID_NDK_PATH="/dir/to/android-ndk-r10c"
+
+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.
# Troubleshooting
@@ -123,6 +175,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, open the class MapFragment and look for the line with `mMap.setAccessToken("access token goes here");`.
+
# 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-ndk b/android-ndk
new file mode 160000
+Subproject e32ad08df1058ec611c771eb1dad234eb886a1b
diff --git a/android/LICENSE_COFFEECATCH b/android/LICENSE_COFFEECATCH
new file mode 100644
index 0000000000..1a44ae19c7
--- /dev/null
+++ b/android/LICENSE_COFFEECATCH
@@ -0,0 +1,23 @@
+Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
+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_COMMONS.txt b/android/LICENSE_COMMONS.txt
new file mode 100644
index 0000000000..d645695673
--- /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 0000000000..d2d488c6e1
--- /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/cpp/coffeecatch.c b/android/cpp/coffeecatch.c
new file mode 100644
index 0000000000..c5428aedc2
--- /dev/null
+++ b/android/cpp/coffeecatch.c
@@ -0,0 +1,1215 @@
+/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.
+ * (especially for Android/Dalvik)
+ *
+ * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. 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 OWNER 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.
+ */
+
+#ifdef __ANDROID__
+#define USE_UNWIND
+#define USE_CORKSCREW
+#endif
+
+/* #undef NO_USE_SIGALTSTACK */
+/* #undef USE_SILENT_SIGALTSTACK */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <sys/ucontext.h>
+#if (defined(USE_UNWIND) && !defined(USE_CORKSCREW))
+#include <unwind.h>
+#endif
+#include <pthread.h>
+#include <dlfcn.h>
+#include <coffeecatch/coffeecatch.h>
+
+/*#define NDK_DEBUG 1*/
+#if ( defined(NDK_DEBUG) && ( NDK_DEBUG == 1 ) )
+#define DEBUG(A) do { A; } while(0)
+#define FD_ERRNO 2
+static void print(const char *const s) {
+ size_t count;
+ for(count = 0; s[count] != '\0'; count++) ;
+ /* write() is async-signal-safe. */
+ (void) write(FD_ERRNO, s, count);
+}
+#else
+#define DEBUG(A)
+#endif
+
+/* Alternative stack size. */
+#define SIG_STACK_BUFFER_SIZE SIGSTKSZ
+
+#ifdef USE_UNWIND
+/* Number of backtraces to get. */
+#define BACKTRACE_FRAMES_MAX 32
+#endif
+
+/* Signals to be caught. */
+#define SIG_CATCH_COUNT 7
+static const int native_sig_catch[SIG_CATCH_COUNT + 1]
+ = { SIGABRT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV
+#ifdef SIGSTKFLT
+ , SIGSTKFLT
+#endif
+ , 0 };
+
+/* Maximum value of a caught signal. */
+#define SIG_NUMBER_MAX 32
+
+#ifdef USE_CORKSCREW
+typedef struct map_info_t map_info_t;
+/* Extracted from Android's include/corkscrew/backtrace.h */
+typedef struct {
+ uintptr_t absolute_pc;
+ uintptr_t stack_top;
+ size_t stack_size;
+} backtrace_frame_t;
+typedef struct {
+ uintptr_t relative_pc;
+ uintptr_t relative_symbol_addr;
+ char* map_name;
+ char* symbol_name;
+ char* demangled_name;
+} backtrace_symbol_t;
+/* Extracted from Android's libcorkscrew/arch-arm/backtrace-arm.c */
+typedef ssize_t (*t_unwind_backtrace_signal_arch)
+(siginfo_t* si, void* sc, const map_info_t* lst, backtrace_frame_t* bt,
+size_t ignore_depth, size_t max_depth);
+typedef map_info_t* (*t_acquire_my_map_info_list)();
+typedef void (*t_release_my_map_info_list)(map_info_t* milist);
+typedef void (*t_get_backtrace_symbols)(const backtrace_frame_t* backtrace,
+ size_t frames,
+ backtrace_symbol_t* symbols);
+typedef void (*t_free_backtrace_symbols)(backtrace_symbol_t* symbols,
+ size_t frames);
+#endif
+
+/* Process-wide crash handler structure. */
+typedef struct native_code_global_struct {
+ /* Initialized. */
+ int initialized;
+
+ /* Lock. */
+ pthread_mutex_t mutex;
+
+ /* Backup of sigaction. */
+ struct sigaction *sa_old;
+} native_code_global_struct;
+#define NATIVE_CODE_GLOBAL_INITIALIZER { 0, PTHREAD_MUTEX_INITIALIZER, NULL }
+
+/* Thread-specific crash handler structure. */
+typedef struct native_code_handler_struct {
+ /* Restore point context. */
+ sigjmp_buf ctx;
+ int ctx_is_set;
+
+ /* Alternate stack. */
+ char *stack_buffer;
+ size_t stack_buffer_size;
+ stack_t stack_old;
+
+ /* Signal code and info. */
+ int code;
+ siginfo_t si;
+ ucontext_t uc;
+
+ /* Uwind context. */
+#if (defined(USE_CORKSCREW))
+ backtrace_frame_t frames[BACKTRACE_FRAMES_MAX];
+#elif (defined(USE_UNWIND))
+ uintptr_t frames[BACKTRACE_FRAMES_MAX];
+#endif
+ size_t frames_size;
+ size_t frames_skip;
+
+ /* Custom assertion failures. */
+ const char *expression;
+ const char *file;
+ int line;
+
+ /* Alarm was fired. */
+ int alarm;
+} native_code_handler_struct;
+
+/* Global crash handler structure. */
+static native_code_global_struct native_code_g =
+ NATIVE_CODE_GLOBAL_INITIALIZER;
+
+/* Thread variable holding context. */
+pthread_key_t native_code_thread;
+
+#if (defined(USE_UNWIND) && !defined(USE_CORKSCREW))
+/* Unwind callback */
+static _Unwind_Reason_Code
+coffeecatch_unwind_callback(struct _Unwind_Context* context, void* arg) {
+ native_code_handler_struct *const s = (native_code_handler_struct*) arg;
+
+ const uintptr_t ip = _Unwind_GetIP(context);
+
+ DEBUG(print("called unwind callback\n"));
+
+ if (ip != 0x0) {
+ if (s->frames_skip == 0) {
+ s->frames[s->frames_size] = ip;
+ s->frames_size++;
+ } else {
+ s->frames_skip--;
+ }
+ }
+
+ if (s->frames_size == BACKTRACE_FRAMES_MAX) {
+ return _URC_END_OF_STACK;
+ } else {
+ DEBUG(print("returned _URC_OK\n"));
+ return _URC_OK;
+ }
+}
+#endif
+
+/* Use libcorkscrew to get a backtrace inside a signal handler.
+ Will only return a non-zero code on Android >= 4 (with libcorkscrew.so
+ being shipped) */
+#ifdef USE_CORKSCREW
+static ssize_t coffeecatch_backtrace_signal(siginfo_t* si, void* sc,
+ backtrace_frame_t* frames,
+ size_t ignore_depth,
+ size_t max_depth) {
+ void *const libcorkscrew = dlopen("libcorkscrew.so", RTLD_LAZY | RTLD_LOCAL);
+ if (libcorkscrew != NULL) {
+ t_unwind_backtrace_signal_arch unwind_backtrace_signal_arch
+ = (t_unwind_backtrace_signal_arch)
+ dlsym(libcorkscrew, "unwind_backtrace_signal_arch");
+ t_acquire_my_map_info_list acquire_my_map_info_list
+ = (t_acquire_my_map_info_list)
+ dlsym(libcorkscrew, "acquire_my_map_info_list");
+ t_release_my_map_info_list release_my_map_info_list
+ = (t_release_my_map_info_list)
+ dlsym(libcorkscrew, "release_my_map_info_list");
+ if (unwind_backtrace_signal_arch != NULL
+ && acquire_my_map_info_list != NULL
+ && release_my_map_info_list != NULL) {
+ map_info_t*const info = acquire_my_map_info_list();
+ const ssize_t size =
+ unwind_backtrace_signal_arch(si, sc, info, frames, ignore_depth,
+ max_depth);
+ release_my_map_info_list(info);
+ return size;
+ } else {
+ DEBUG(print("symbols not founs in libcorkscrew.so\n"));
+ }
+ dlclose(libcorkscrew);
+ } else {
+ DEBUG(print("libcorkscrew.so could not be loaded\n"));
+ }
+ return -1;
+}
+
+static void coffeecatch_backtrace_symbols(const backtrace_frame_t* backtrace,
+ size_t frames,
+ void (*fun)(void *arg,
+ const backtrace_symbol_t *sym),
+ void *arg) {
+ void *const libcorkscrew = dlopen("libcorkscrew.so", RTLD_LAZY | RTLD_LOCAL);
+ if (libcorkscrew != NULL) {
+ t_get_backtrace_symbols get_backtrace_symbols
+ = (t_get_backtrace_symbols)
+ dlsym(libcorkscrew, "get_backtrace_symbols");
+ t_free_backtrace_symbols free_backtrace_symbols
+ = (t_free_backtrace_symbols)
+ dlsym(libcorkscrew, "free_backtrace_symbols");
+ if (get_backtrace_symbols != NULL
+ && free_backtrace_symbols != NULL) {
+ backtrace_symbol_t symbols[BACKTRACE_FRAMES_MAX];
+ size_t i;
+ if (frames > BACKTRACE_FRAMES_MAX) {
+ frames = BACKTRACE_FRAMES_MAX;
+ }
+ get_backtrace_symbols(backtrace, frames, symbols);
+ for(i = 0; i < frames; i++) {
+ fun(arg, &symbols[i]);
+ }
+ free_backtrace_symbols(symbols, frames);
+ }
+ dlclose(libcorkscrew);
+ }
+}
+#endif
+
+/* Call the old handler. */
+static void coffeecatch_call_old_signal_handler(const int code, siginfo_t *const si,
+ void * const sc) {
+ /* Call the "real" Java handler for JIT and internals. */
+ if (code >= 0 && code < SIG_NUMBER_MAX) {
+ if (native_code_g.sa_old[code].sa_sigaction != NULL) {
+ native_code_g.sa_old[code].sa_sigaction(code, si, sc);
+ } else if (native_code_g.sa_old[code].sa_handler != NULL) {
+ native_code_g.sa_old[code].sa_handler(code);
+ }
+ }
+}
+
+/* Unflag "on stack" */
+static void coffeecatch_revert_alternate_stack(void) {
+#ifndef NO_USE_SIGALTSTACK
+ stack_t ss;
+ if (sigaltstack(NULL, &ss) == 0) {
+ ss.ss_flags &= ~SS_ONSTACK;
+ sigaltstack (&ss, NULL);
+ }
+#endif
+}
+
+/* Try to jump to userland. */
+static void coffeecatch_try_jump_userland(native_code_handler_struct*
+ const t,
+ const int code,
+ siginfo_t *const si,
+ void * const sc) {
+ (void) si; /* UNUSED */
+ (void) sc; /* UNUSED */
+
+ /* Valid context ? */
+ if (t != NULL && t->ctx_is_set) {
+ DEBUG(print("calling siglongjmp()\n"));
+
+ /* Invalidate the context */
+ t->ctx_is_set = 0;
+
+ /* We need to revert the alternate stack before jumping. */
+ coffeecatch_revert_alternate_stack();
+
+ /*
+ * Note on async-signal-safety of siglongjmp() [POSIX] :
+ * "Note that longjmp() and siglongjmp() are not in the list of
+ * async-signal-safe functions. This is because the code executing after
+ * longjmp() and siglongjmp() can call any unsafe functions with the same
+ * danger as calling those unsafe functions directly from the signal
+ * handler. Applications that use longjmp() and siglongjmp() from within
+ * signal handlers require rigorous protection in order to be portable.
+ * Many of the other functions that are excluded from the list are
+ * traditionally implemented using either malloc() or free() functions or
+ * the standard I/O library, both of which traditionally use data
+ * structures in a non-async-signal-safe manner. Since any combination of
+ * different functions using a common data structure can cause
+ * async-signal-safety problems, this volume of POSIX.1-2008 does not
+ * define the behavior when any unsafe function is called in a signal
+ * handler that interrupts an unsafe function."
+ */
+ siglongjmp(t->ctx, code);
+ }
+}
+
+static void coffeecatch_start_alarm(void) {
+ /* Ensure we do not deadlock. Default of ALRM is to die.
+ * (signal() and alarm() are signal-safe) */
+ (void) alarm(30);
+}
+
+static void coffeecatch_mark_alarm(native_code_handler_struct *const t) {
+ t->alarm = 1;
+}
+
+/* Copy context infos (signal code, etc.) */
+static void coffeecatch_copy_context(native_code_handler_struct *const t,
+ const int code, siginfo_t *const si,
+ void *const sc) {
+ t->code = code;
+ t->si = *si;
+ if (sc != NULL) {
+ ucontext_t *const uc = (ucontext_t*) sc;
+ t->uc = *uc;
+ } else {
+ memset(&t->uc, 0, sizeof(t->uc));
+ }
+
+#ifdef USE_UNWIND
+ /* Frame buffer initial position. */
+ t->frames_size = 0;
+
+ /* Skip us and the caller. */
+ t->frames_skip = 2;
+
+ /* Use the corkscrew library to extract the backtrace. */
+#ifdef USE_CORKSCREW
+ t->frames_size = coffeecatch_backtrace_signal(si, sc, t->frames, 0,
+ BACKTRACE_FRAMES_MAX);
+#else
+ /* Unwind frames (equivalent to backtrace()) */
+ _Unwind_Backtrace(coffeecatch_unwind_callback, t);
+#endif
+
+ if (t->frames_size != 0) {
+ DEBUG(print("called _Unwind_Backtrace()\n"));
+ } else {
+ DEBUG(print("called _Unwind_Backtrace(), but no traces\n"));
+ }
+#endif
+}
+
+/* Return the thread-specific native_code_handler_struct structure, or
+ * @c null if no such structure is available. */
+static native_code_handler_struct* coffeecatch_get() {
+ return (native_code_handler_struct*)
+ pthread_getspecific(native_code_thread);
+}
+
+int coffeecatch_cancel_pending_alarm() {
+ native_code_handler_struct *const t = coffeecatch_get();
+ if (t != NULL && t->alarm) {
+ t->alarm = 0;
+ /* "If seconds is 0, a pending alarm request, if any, is canceled." */
+ alarm(0);
+ return 0;
+ }
+ return -1;
+}
+
+/* Internal signal pass-through. Allows to peek the "real" crash before
+ * calling the Java handler. Remember than Java needs many of the signals
+ * (for the JIT, for test-free NullPointerException handling, etc.)
+ * We record the siginfo_t context in this function each time it is being
+ * called, to be able to know what error caused an issue.
+ */
+static void coffeecatch_signal_pass(const int code, siginfo_t *const si,
+ void *const sc) {
+ native_code_handler_struct *t;
+
+ DEBUG(print("caught signal\n"));
+
+ /* Call the "real" Java handler for JIT and internals. */
+ coffeecatch_call_old_signal_handler(code, si, sc);
+
+ /* Still here ?
+ * FIXME TODO: This is the Dalvik behavior - but is it the SunJVM one ? */
+
+ /* Ensure we do not deadlock. Default of ALRM is to die.
+ * (signal() and alarm() are signal-safe) */
+ signal(code, SIG_DFL);
+ coffeecatch_start_alarm();
+
+ /* Available context ? */
+ t = coffeecatch_get();
+ if (t != NULL) {
+ /* An alarm() call was triggered. */
+ coffeecatch_mark_alarm(t);
+
+ /* Take note of the signal. */
+ coffeecatch_copy_context(t, code, si, sc);
+
+ /* Back to the future. */
+ coffeecatch_try_jump_userland(t, code, si, sc);
+ }
+
+ /* Nope. (abort() is signal-safe) */
+ DEBUG(print("calling abort()\n"));
+ signal(SIGABRT, SIG_DFL);
+ abort();
+}
+
+/* Internal crash handler for abort(). Java calls abort() if its signal handler
+ * could not resolve the signal ; thus calling us through this handler. */
+static void coffeecatch_signal_abort(const int code, siginfo_t *const si,
+ void *const sc) {
+ native_code_handler_struct *t;
+
+ (void) sc; /* UNUSED */
+
+ DEBUG(print("caught abort\n"));
+
+ /* Ensure we do not deadlock. Default of ALRM is to die.
+ * (signal() and alarm() are signal-safe) */
+ signal(code, SIG_DFL);
+ coffeecatch_start_alarm();
+
+ /* Available context ? */
+ t = coffeecatch_get();
+ if (t != NULL) {
+ /* An alarm() call was triggered. */
+ coffeecatch_mark_alarm(t);
+
+ /* Take note (real "abort()") */
+ coffeecatch_copy_context(t, code, si, sc);
+
+ /* Back to the future. */
+ coffeecatch_try_jump_userland(t, code, si, sc);
+ }
+
+ /* No such restore point, call old signal handler then. */
+ DEBUG(print("calling old signal handler\n"));
+ coffeecatch_call_old_signal_handler(code, si, sc);
+
+ /* Nope. (abort() is signal-safe) */
+ DEBUG(print("calling abort()\n"));
+ abort();
+}
+
+/* Internal globals initialization. */
+static int coffeecatch_handler_setup_global(void) {
+ if (native_code_g.initialized++ == 0) {
+ size_t i;
+ struct sigaction sa_abort;
+ struct sigaction sa_pass;
+
+ DEBUG(print("installing global signal handlers\n"));
+
+ /* Setup handler structure. */
+ memset(&sa_abort, 0, sizeof(sa_abort));
+ sigemptyset(&sa_abort.sa_mask);
+ sa_abort.sa_sigaction = coffeecatch_signal_abort;
+ sa_abort.sa_flags = SA_SIGINFO | SA_ONSTACK;
+
+ memset(&sa_pass, 0, sizeof(sa_pass));
+ sigemptyset(&sa_pass.sa_mask);
+ sa_pass.sa_sigaction = coffeecatch_signal_pass;
+ sa_pass.sa_flags = SA_SIGINFO | SA_ONSTACK;
+
+ /* Allocate */
+ native_code_g.sa_old = calloc(sizeof(struct sigaction), SIG_NUMBER_MAX);
+ if (native_code_g.sa_old == NULL) {
+ return -1;
+ }
+
+ /* Setup signal handlers for SIGABRT (Java calls abort()) and others. **/
+ for (i = 0; native_sig_catch[i] != 0; i++) {
+ const int sig = native_sig_catch[i];
+ const struct sigaction * const action =
+ sig == SIGABRT ? &sa_abort : &sa_pass;
+ assert(sig < SIG_NUMBER_MAX);
+ if (sigaction(sig, action, &native_code_g.sa_old[sig]) != 0) {
+ return -1;
+ }
+ }
+
+ /* Initialize thread var. */
+ if (pthread_key_create(&native_code_thread, NULL) != 0) {
+ return -1;
+ }
+
+ DEBUG(print("installed global signal handlers\n"));
+ }
+
+ /* OK. */
+ return 0;
+}
+
+/**
+ * Free a native_code_handler_struct structure.
+ **/
+static int coffeecatch_native_code_handler_struct_free(native_code_handler_struct *const t) {
+ int code = 0;
+
+ if (t == NULL) {
+ return -1;
+ }
+
+#ifndef NO_USE_SIGALTSTACK
+ /* Restore previous alternative stack. */
+ if (t->stack_old.ss_sp != NULL && sigaltstack(&t->stack_old, NULL) != 0) {
+#ifndef USE_SILENT_SIGALTSTACK
+ code = -1;
+#endif
+ }
+#endif
+
+ /* Free alternative stack */
+ if (t->stack_buffer != NULL) {
+ free(t->stack_buffer);
+ t->stack_buffer = NULL;
+ t->stack_buffer_size = 0;
+ }
+
+ /* Free structure. */
+ free(t);
+
+ return code;
+}
+
+/**
+ * Create a native_code_handler_struct structure.
+ **/
+static native_code_handler_struct* coffeecatch_native_code_handler_struct_init(void) {
+ stack_t stack;
+ native_code_handler_struct *const t =
+ calloc(sizeof(native_code_handler_struct), 1);
+
+ if (t == NULL) {
+ return NULL;
+ }
+
+ DEBUG(print("installing thread alternative stack\n"));
+
+ /* Initialize structure */
+ t->stack_buffer_size = SIG_STACK_BUFFER_SIZE;
+ t->stack_buffer = malloc(t->stack_buffer_size);
+ if (t->stack_buffer == NULL) {
+ coffeecatch_native_code_handler_struct_free(t);
+ return NULL;
+ }
+
+ /* Setup alternative stack. */
+ memset(&stack, 0, sizeof(stack));
+ stack.ss_sp = t->stack_buffer;
+ stack.ss_size = t->stack_buffer_size;
+ stack.ss_flags = 0;
+
+#ifndef NO_USE_SIGALTSTACK
+ /* Install alternative stack. This is thread-safe */
+ if (sigaltstack(&stack, &t->stack_old) != 0) {
+#ifndef USE_SILENT_SIGALTSTACK
+ coffeecatch_native_code_handler_struct_free(t);
+ return NULL;
+#endif
+ }
+#endif
+
+ return t;
+}
+
+/**
+ * Acquire the crash handler for the current thread.
+ * The coffeecatch_handler_cleanup() must be called to release allocated
+ * resources.
+ **/
+static int coffeecatch_handler_setup(int setup_thread) {
+ int code;
+
+ DEBUG(print("setup for a new handler\n"));
+
+ /* Initialize globals. */
+ if (pthread_mutex_lock(&native_code_g.mutex) != 0) {
+ return -1;
+ }
+ code = coffeecatch_handler_setup_global();
+ if (pthread_mutex_unlock(&native_code_g.mutex) != 0) {
+ return -1;
+ }
+
+ /* Global initialization failed. */
+ if (code != 0) {
+ return -1;
+ }
+
+ /* Initialize locals. */
+ if (setup_thread && coffeecatch_get() == NULL) {
+ native_code_handler_struct *const t =
+ coffeecatch_native_code_handler_struct_init();
+
+ if (t == NULL) {
+ return -1;
+ }
+
+ DEBUG(print("installing thread alternative stack\n"));
+
+ /* Set thread-specific value. */
+ if (pthread_setspecific(native_code_thread, t) != 0) {
+ coffeecatch_native_code_handler_struct_free(t);
+ return -1;
+ }
+
+ DEBUG(print("installed thread alternative stack\n"));
+ }
+
+ /* OK. */
+ return 0;
+}
+
+/**
+ * Release the resources allocated by a previous call to
+ * coffeecatch_handler_setup().
+ * This function must be called as many times as
+ * coffeecatch_handler_setup() was called to fully release allocated
+ * resources.
+ **/
+static int coffeecatch_handler_cleanup() {
+ /* Cleanup locals. */
+ native_code_handler_struct *const t = coffeecatch_get();
+ if (t != NULL) {
+ DEBUG(print("removing thread alternative stack\n"));
+
+ /* Erase thread-specific value now (detach). */
+ if (pthread_setspecific(native_code_thread, NULL) != 0) {
+ assert(! "pthread_setspecific() failed");
+ }
+
+ /* Free handler and reset slternate stack */
+ if (coffeecatch_native_code_handler_struct_free(t) != 0) {
+ return -1;
+ }
+
+ DEBUG(print("removed thread alternative stack\n"));
+ }
+
+ /* Cleanup globals. */
+ if (pthread_mutex_lock(&native_code_g.mutex) != 0) {
+ assert(! "pthread_mutex_lock() failed");
+ }
+ assert(native_code_g.initialized != 0);
+ if (--native_code_g.initialized == 0) {
+ size_t i;
+
+ DEBUG(print("removing global signal handlers\n"));
+
+ /* Restore signal handler. */
+ for(i = 0; native_sig_catch[i] != 0; i++) {
+ const int sig = native_sig_catch[i];
+ assert(sig < SIG_NUMBER_MAX);
+ if (sigaction(sig, &native_code_g.sa_old[sig], NULL) != 0) {
+ return -1;
+ }
+ }
+
+ /* Free old structure. */
+ free(native_code_g.sa_old);
+ native_code_g.sa_old = NULL;
+
+ /* Delete thread var. */
+ if (pthread_key_delete(native_code_thread) != 0) {
+ assert(! "pthread_key_delete() failed");
+ }
+
+ DEBUG(print("removed global signal handlers\n"));
+ }
+ if (pthread_mutex_unlock(&native_code_g.mutex) != 0) {
+ assert(! "pthread_mutex_unlock() failed");
+ }
+
+ return 0;
+}
+
+/**
+ * Get the signal associated with the crash.
+ */
+int coffeecatch_get_signal() {
+ const native_code_handler_struct* const t = coffeecatch_get();
+ if (t != NULL) {
+ return t->code;
+ } else {
+ return -1;
+ }
+}
+
+/* Signal descriptions.
+ See <http://pubs.opengroup.org/onlinepubs/009696699/basedefs/signal.h.html>
+*/
+static const char* coffeecatch_desc_sig(int sig, int code) {
+ switch(sig) {
+ case SIGILL:
+ switch(code) {
+ case ILL_ILLOPC:
+ return "Illegal opcode";
+ case ILL_ILLOPN:
+ return "Illegal operand";
+ case ILL_ILLADR:
+ return "Illegal addressing mode";
+ case ILL_ILLTRP:
+ return "Illegal trap";
+ case ILL_PRVOPC:
+ return "Privileged opcode";
+ case ILL_PRVREG:
+ return "Privileged register";
+ case ILL_COPROC:
+ return "Coprocessor error";
+ case ILL_BADSTK:
+ return "Internal stack error";
+ default:
+ return "Illegal operation";
+ }
+ break;
+ case SIGFPE:
+ switch(code) {
+ case FPE_INTDIV:
+ return "Integer divide by zero";
+ case FPE_INTOVF:
+ return "Integer overflow";
+ case FPE_FLTDIV:
+ return "Floating-point divide by zero";
+ case FPE_FLTOVF:
+ return "Floating-point overflow";
+ case FPE_FLTUND:
+ return "Floating-point underflow";
+ case FPE_FLTRES:
+ return "Floating-point inexact result";
+ case FPE_FLTINV:
+ return "Invalid floating-point operation";
+ case FPE_FLTSUB:
+ return "Subscript out of range";
+ default:
+ return "Floating-point";
+ }
+ break;
+ case SIGSEGV:
+ switch(code) {
+ case SEGV_MAPERR:
+ return "Address not mapped to object";
+ case SEGV_ACCERR:
+ return "Invalid permissions for mapped object";
+ default:
+ return "Segmentation violation";
+ }
+ break;
+ case SIGBUS:
+ switch(code) {
+ case BUS_ADRALN:
+ return "Invalid address alignment";
+ case BUS_ADRERR:
+ return "Nonexistent physical address";
+ case BUS_OBJERR:
+ return "Object-specific hardware error";
+ default:
+ return "Bus error";
+ }
+ break;
+ case SIGTRAP:
+ switch(code) {
+ case TRAP_BRKPT:
+ return "Process breakpoint";
+ case TRAP_TRACE:
+ return "Process trace trap";
+ default:
+ return "Trap";
+ }
+ break;
+ case SIGCHLD:
+ switch(code) {
+ case CLD_EXITED:
+ return "Child has exited";
+ case CLD_KILLED:
+ return "Child has terminated abnormally and did not create a core file";
+ case CLD_DUMPED:
+ return "Child has terminated abnormally and created a core file";
+ case CLD_TRAPPED:
+ return "Traced child has trapped";
+ case CLD_STOPPED:
+ return "Child has stopped";
+ case CLD_CONTINUED:
+ return "Stopped child has continued";
+ default:
+ return "Child";
+ }
+ break;
+ case SIGPOLL:
+ switch(code) {
+ case POLL_IN:
+ return "Data input available";
+ case POLL_OUT:
+ return "Output buffers available";
+ case POLL_MSG:
+ return "Input message available";
+ case POLL_ERR:
+ return "I/O error";
+ case POLL_PRI:
+ return "High priority input available";
+ case POLL_HUP:
+ return "Device disconnected";
+ default:
+ return "Pool";
+ }
+ break;
+ case SIGABRT:
+ return "Process abort signal";
+ case SIGALRM:
+ return "Alarm clock";
+ case SIGCONT:
+ return "Continue executing, if stopped";
+ case SIGHUP:
+ return "Hangup";
+ case SIGINT:
+ return "Terminal interrupt signal";
+ case SIGKILL:
+ return "Kill";
+ case SIGPIPE:
+ return "Write on a pipe with no one to read it";
+ case SIGQUIT:
+ return "Terminal quit signal";
+ case SIGSTOP:
+ return "Stop executing";
+ case SIGTERM:
+ return "Termination signal";
+ case SIGTSTP:
+ return "Terminal stop signal";
+ case SIGTTIN:
+ return "Background process attempting read";
+ case SIGTTOU:
+ return "Background process attempting write";
+ case SIGUSR1:
+ return "User-defined signal 1";
+ case SIGUSR2:
+ return "User-defined signal 2";
+ case SIGPROF:
+ return "Profiling timer expired";
+ case SIGSYS:
+ return "Bad system call";
+ case SIGVTALRM:
+ return "Virtual timer expired";
+ case SIGURG:
+ return "High bandwidth data is available at a socket";
+ case SIGXCPU:
+ return "CPU time limit exceeded";
+ case SIGXFSZ:
+ return "File size limit exceeded";
+ default:
+ switch(code) {
+ case SI_USER:
+ return "Signal sent by kill()";
+ case SI_QUEUE:
+ return "Signal sent by the sigqueue()";
+ case SI_TIMER:
+ return "Signal generated by expiration of a timer set by timer_settime()";
+ case SI_ASYNCIO:
+ return "Signal generated by completion of an asynchronous I/O request";
+ case SI_MESGQ:
+ return
+ "Signal generated by arrival of a message on an empty message queue";
+ default:
+ return "Unknown signal";
+ }
+ break;
+ }
+}
+
+/**
+ * Get the backtrace size. Returns 0 if no backtrace is available.
+ */
+size_t coffeecatch_get_backtrace_size(void) {
+#ifdef USE_UNWIND
+ const native_code_handler_struct* const t = coffeecatch_get();
+ if (t != NULL) {
+ return t->frames_size;
+ } else {
+ return 0;
+ }
+#else
+ return 0;
+#endif
+}
+
+/**
+ * Get the <index>th element of the backtrace, or 0 upon error.
+ */
+uintptr_t coffeecatch_get_backtrace(ssize_t index) {
+#ifdef USE_UNWIND
+ const native_code_handler_struct* const t = coffeecatch_get();
+ if (t != NULL) {
+ if (index < 0) {
+ index = t->frames_size + index;
+ }
+ if (index >= 0 && (size_t) index < t->frames_size) {
+#ifdef USE_CORKSCREW
+ return t->frames[index].absolute_pc;
+#else
+ return t->frames[index];
+#endif
+ }
+ }
+#else
+ (void) index;
+#endif
+ return 0;
+}
+
+/**
+ * Get the program counter, given a pointer to a ucontext_t context.
+ **/
+static uintptr_t coffeecatch_get_pc_from_ucontext(const ucontext_t *uc) {
+#if (defined(__arm__))
+ return uc->uc_mcontext.arm_pc;
+#elif (defined(__x86_64__))
+ return uc->uc_mcontext.gregs[REG_RIP];
+#elif (defined(__i386))
+ return uc->uc_mcontext.gregs[REG_EIP];
+#elif (defined (__ppc__)) || (defined (__powerpc__))
+ return uc->uc_mcontext.regs->nip;
+#elif (defined(__hppa__))
+ return uc->uc_mcontext.sc_iaoq[0] & ~0x3UL;
+#elif (defined(__sparc__) && defined (__arch64__))
+ return uc->uc_mcontext.mc_gregs[MC_PC];
+#elif (defined(__sparc__) && !defined (__arch64__))
+ return uc->uc_mcontext.gregs[REG_PC];
+#elif (defined(__mips__))
+ return uc->uc_mcontext.gregs[31];
+#else
+#error "Architecture is unknown, please report me!"
+#endif
+}
+
+/* Is this module name look like a DLL ?
+ FIXME: find a better way to do that... */
+static int coffeecatch_is_dll(const char *name) {
+ size_t i;
+ for(i = 0; name[i] != '\0'; i++) {
+ if (name[i + 0] == '.' &&
+ name[i + 1] == 's' &&
+ name[i + 2] == 'o' &&
+ ( name[i + 3] == '\0' || name[i + 3] == '.') ) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Extract a line information on a PC address. */
+static void format_pc_address_cb(uintptr_t pc,
+ void (*fun)(void *arg, const char *module,
+ uintptr_t addr,
+ const char *function,
+ uintptr_t offset), void *arg) {
+ if (pc != 0) {
+ Dl_info info;
+ void * const addr = (void*) pc;
+ /* dladdr() returns 0 on error, and nonzero on success. */
+ if (dladdr(addr, &info) != 0 && info.dli_fname != NULL) {
+ const uintptr_t near = (uintptr_t) info.dli_saddr;
+ const uintptr_t offs = pc - near;
+ const uintptr_t addr_rel = pc - (uintptr_t) info.dli_fbase;
+ /* We need the absolute address for the main module (?).
+ TODO FIXME to be investigated. */
+ const uintptr_t addr_to_use = coffeecatch_is_dll(info.dli_fname)
+ ? addr_rel : pc;
+ fun(arg, info.dli_fname, addr_to_use, info.dli_sname, offs);
+ } else {
+ fun(arg, NULL, pc, NULL, 0);
+ }
+ }
+}
+
+typedef struct t_print_fun {
+ char *buffer;
+ size_t buffer_size;
+} t_print_fun;
+
+static void print_fun(void *arg, const char *module, uintptr_t uaddr,
+ const char *function, uintptr_t offset) {
+ t_print_fun *const t = (t_print_fun*) arg;
+ char *const buffer = t->buffer;
+ const size_t buffer_size = t->buffer_size;
+ const void*const addr = (void*) uaddr;
+ if (module == NULL) {
+ snprintf(buffer, buffer_size, "[at %p]", addr);
+ } else if (function != NULL) {
+ snprintf(buffer, buffer_size, "[at %s:%p (%s+0x%x)]", module, addr,
+ function, (int) offset);
+ } else {
+ snprintf(buffer, buffer_size, "[at %s:%p]", module, addr);
+ }
+}
+
+/* Format a line information on a PC address. */
+static void format_pc_address(char *buffer, size_t buffer_size, uintptr_t pc) {
+ t_print_fun t;
+ t.buffer = buffer;
+ t.buffer_size = buffer_size;
+ format_pc_address_cb(pc, print_fun, &t);
+}
+
+/**
+ * Get the full error message associated with the crash.
+ */
+const char* coffeecatch_get_message() {
+ const int error = errno;
+ const native_code_handler_struct* const t = coffeecatch_get();
+
+ /* Found valid handler. */
+ if (t != NULL) {
+ char * const buffer = t->stack_buffer;
+ const size_t buffer_len = t->stack_buffer_size;
+ size_t buffer_offs = 0;
+
+ const char* const posix_desc =
+ coffeecatch_desc_sig(t->si.si_signo, t->si.si_code);
+
+ /* Assertion failure ? */
+ if ((t->code == SIGABRT
+#ifdef __ANDROID__
+ /* See Android BUG #16672:
+ * "C assert() failure causes SIGSEGV when it should cause SIGABRT" */
+ || (t->code == SIGSEGV && (uintptr_t) t->si.si_addr == 0xdeadbaad)
+#endif
+ ) && t->expression != NULL) {
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs,
+ "assertion '%s' failed at %s:%d",
+ t->expression, t->file, t->line);
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ }
+ /* Signal */
+ else {
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, "signal %d",
+ t->si.si_signo);
+ buffer_offs += strlen(&buffer[buffer_offs]);
+
+ /* Description */
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, " (%s)",
+ posix_desc);
+ buffer_offs += strlen(&buffer[buffer_offs]);
+
+ /* Address of faulting instruction */
+ if (t->si.si_signo == SIGILL || t->si.si_signo == SIGSEGV) {
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, " at address %p",
+ t->si.si_addr);
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ }
+ }
+
+ /* [POSIX] If non-zero, an errno value associated with this signal,
+ as defined in <errno.h>. */
+ if (t->si.si_errno != 0) {
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, ": ");
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ if (strerror_r(t->si.si_errno, &buffer[buffer_offs],
+ buffer_len - buffer_offs) == 0) {
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs,
+ "unknown error");
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ }
+ }
+
+ /* Sending process ID. */
+ if (t->si.si_signo == SIGCHLD && t->si.si_pid != 0) {
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs,
+ " (sent by pid %d)", (int) t->si.si_pid);
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ }
+
+ /* Faulting program counter location. */
+ if (coffeecatch_get_pc_from_ucontext(&t->uc) != 0) {
+ const uintptr_t pc = coffeecatch_get_pc_from_ucontext(&t->uc);
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, " ");
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ format_pc_address(&buffer[buffer_offs], buffer_len - buffer_offs, pc);
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ }
+
+ /* Return string. */
+ buffer[buffer_offs] = '\0';
+ return t->stack_buffer;
+ } else {
+ /* Static buffer in case of emergency */
+ static char buffer[256];
+#ifdef _GNU_SOURCE
+ return strerror_r(error, &buffer[0], sizeof(buffer));
+#else
+ const int code = strerror_r(error, &buffer[0], sizeof(buffer));
+ errno = error;
+ if (code == 0) {
+ return buffer;
+ } else {
+ return "unknown error during crash handler setup";
+ }
+#endif
+ }
+}
+
+#if (defined(USE_CORKSCREW))
+typedef struct t_coffeecatch_backtrace_symbols_fun {
+ void (*fun)(void *arg, const char *module, uintptr_t addr,
+ const char *function, uintptr_t offset);
+ void *arg;
+} t_coffeecatch_backtrace_symbols_fun;
+
+static void coffeecatch_backtrace_symbols_fun(void *arg, const backtrace_symbol_t *sym) {
+ t_coffeecatch_backtrace_symbols_fun *const bt =
+ (t_coffeecatch_backtrace_symbols_fun*) arg;
+ const char *symbol = sym->demangled_name != NULL
+ ? sym->demangled_name : sym->symbol_name;
+ const uintptr_t rel = sym->relative_pc - sym->relative_symbol_addr;
+ bt->fun(bt->arg, sym->map_name, sym->relative_pc, symbol, rel);
+}
+#endif
+
+/**
+ * Enumerate backtrace information.
+ */
+void coffeecatch_get_backtrace_info(void (*fun)(void *arg,
+ const char *module,
+ uintptr_t addr,
+ const char *function,
+ uintptr_t offset), void *arg) {
+ const native_code_handler_struct* const t = coffeecatch_get();
+ if (t != NULL) {
+#if (defined(USE_CORKSCREW))
+ t_coffeecatch_backtrace_symbols_fun bt;
+ bt.fun = fun;
+ bt.arg = arg;
+ coffeecatch_backtrace_symbols(t->frames, t->frames_size,
+ coffeecatch_backtrace_symbols_fun, &bt);
+#elif (defined(USE_UNWIND))
+ size_t i;
+ for(i = 0; i < t->frames_size; i++) {
+ const uintptr_t pc = t->frames[i];
+ format_pc_address_cb(pc, fun, arg);
+ }
+#else
+ (void) fun;
+ (void) arg;
+#endif
+ }
+}
+
+/**
+ * Calls coffeecatch_handler_setup(1) to setup a crash handler, mark the
+ * context as valid, and return 0 upon success.
+ */
+int coffeecatch_setup() {
+ if (coffeecatch_handler_setup(1) == 0) {
+ native_code_handler_struct *const t = coffeecatch_get();
+ assert(t != NULL);
+ t->ctx_is_set = 1;
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+/**
+ * Calls coffeecatch_handler_cleanup()
+ */
+void coffeecatch_cleanup() {
+ native_code_handler_struct *const t = coffeecatch_get();
+ assert(t != NULL);
+ t->ctx_is_set = 0;
+ coffeecatch_handler_cleanup();
+}
+
+sigjmp_buf* coffeecatch_get_ctx() {
+ native_code_handler_struct* t = coffeecatch_get();
+ assert(t != NULL);
+ return &t->ctx;
+}
+
+void coffeecatch_abort(const char* exp, const char* file, int line) {
+ native_code_handler_struct *const t = coffeecatch_get();
+ if (t != NULL) {
+ t->expression = exp;
+ t->file = file;
+ t->line = line;
+ }
+ abort();
+}
diff --git a/android/cpp/coffeecatch.c.orig b/android/cpp/coffeecatch.c.orig
new file mode 100644
index 0000000000..1889dc0b9b
--- /dev/null
+++ b/android/cpp/coffeecatch.c.orig
@@ -0,0 +1,1338 @@
+/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.
+ * (especially for Android/Dalvik)
+ *
+ * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. 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 OWNER 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.
+ */
+
+#ifdef __ANDROID__
+#define USE_UNWIND
+#define USE_CORKSCREW
+#endif
+
+/* #undef NO_USE_SIGALTSTACK */
+/* #undef USE_SILENT_SIGALTSTACK */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <signal.h>
+#include <setjmp.h>
+#if defined(__ANDROID__) && !defined(__BIONIC_HAVE_UCONTEXT_T) && \
+ defined(__arm__) && !defined(__BIONIC_HAVE_STRUCT_SIGCONTEXT)
+#include <asm/sigcontext.h>
+#endif
+#if (defined(USE_UNWIND) && !defined(USE_CORKSCREW))
+#include <unwind.h>
+#endif
+#include <pthread.h>
+#include <dlfcn.h>
+#include <coffeecatch/coffeecatch.h>
+
+/*#define NDK_DEBUG 1*/
+#if ( defined(NDK_DEBUG) && ( NDK_DEBUG == 1 ) )
+#define DEBUG(A) do { A; } while(0)
+#define FD_ERRNO 2
+static void print(const char *const s) {
+ size_t count;
+ for(count = 0; s[count] != '\0'; count++) ;
+ /* write() is async-signal-safe. */
+ (void) write(FD_ERRNO, s, count);
+}
+#else
+#define DEBUG(A)
+#endif
+
+/* Alternative stack size. */
+#define SIG_STACK_BUFFER_SIZE SIGSTKSZ
+
+#ifdef USE_UNWIND
+/* Number of backtraces to get. */
+#define BACKTRACE_FRAMES_MAX 32
+#endif
+
+/* Signals to be caught. */
+#define SIG_CATCH_COUNT 7
+static const int native_sig_catch[SIG_CATCH_COUNT + 1]
+ = { SIGABRT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV
+#ifdef SIGSTKFLT
+ , SIGSTKFLT
+#endif
+ , 0 };
+
+/* Maximum value of a caught signal. */
+#define SIG_NUMBER_MAX 32
+
+#if (defined(__ANDROID__) && (!defined(__BIONIC_HAVE_UCONTEXT_T)))
+#ifndef ucontext_h_seen
+#define ucontext_h_seen
+
+/* stack_t definition */
+#include <asm/signal.h>
+
+#if defined(__arm__)
+
+/* Taken from richard.quirk's header file. (Android does not have it) */
+
+typedef struct ucontext {
+ unsigned long uc_flags;
+ struct ucontext *uc_link;
+ stack_t uc_stack;
+ struct sigcontext uc_mcontext;
+ unsigned long uc_sigmask;
+} ucontext_t;
+
+#elif defined(__i386__)
+
+/* Taken from Google Breakpad. */
+
+/* 80-bit floating-point register */
+struct _libc_fpreg {
+ unsigned short significand[4];
+ unsigned short exponent;
+};
+
+/* Simple floating-point state, see FNSTENV instruction */
+struct _libc_fpstate {
+ unsigned long cw;
+ unsigned long sw;
+ unsigned long tag;
+ unsigned long ipoff;
+ unsigned long cssel;
+ unsigned long dataoff;
+ unsigned long datasel;
+ struct _libc_fpreg _st[8];
+ unsigned long status;
+};
+
+typedef uint32_t greg_t;
+
+typedef struct {
+ uint32_t gregs[19];
+ struct _libc_fpstate* fpregs;
+ uint32_t oldmask;
+ uint32_t cr2;
+} mcontext_t;
+
+enum {
+ REG_GS = 0,
+ REG_FS,
+ REG_ES,
+ REG_DS,
+ REG_EDI,
+ REG_ESI,
+ REG_EBP,
+ REG_ESP,
+ REG_EBX,
+ REG_EDX,
+ REG_ECX,
+ REG_EAX,
+ REG_TRAPNO,
+ REG_ERR,
+ REG_EIP,
+ REG_CS,
+ REG_EFL,
+ REG_UESP,
+ REG_SS,
+};
+
+typedef struct ucontext {
+ uint32_t uc_flags;
+ struct ucontext* uc_link;
+ stack_t uc_stack;
+ mcontext_t uc_mcontext;
+} ucontext_t;
+
+#elif defined(__mips__)
+
+/* Taken from Google Breakpad. */
+
+typedef struct {
+ uint32_t regmask;
+ uint32_t status;
+ uint64_t pc;
+ uint64_t gregs[32];
+ uint64_t fpregs[32];
+ uint32_t acx;
+ uint32_t fpc_csr;
+ uint32_t fpc_eir;
+ uint32_t used_math;
+ uint32_t dsp;
+ uint64_t mdhi;
+ uint64_t mdlo;
+ uint32_t hi1;
+ uint32_t lo1;
+ uint32_t hi2;
+ uint32_t lo2;
+ uint32_t hi3;
+ uint32_t lo3;
+} mcontext_t;
+
+typedef struct ucontext {
+ uint32_t uc_flags;
+ struct ucontext* uc_link;
+ stack_t uc_stack;
+ mcontext_t uc_mcontext;
+} ucontext_t;
+
+#else
+#error "Architecture is not supported (unknown ucontext layout)"
+#endif
+
+#endif
+
+#ifdef USE_CORKSCREW
+typedef struct map_info_t map_info_t;
+/* Extracted from Android's include/corkscrew/backtrace.h */
+typedef struct {
+ uintptr_t absolute_pc;
+ uintptr_t stack_top;
+ size_t stack_size;
+} backtrace_frame_t;
+typedef struct {
+ uintptr_t relative_pc;
+ uintptr_t relative_symbol_addr;
+ char* map_name;
+ char* symbol_name;
+ char* demangled_name;
+} backtrace_symbol_t;
+/* Extracted from Android's libcorkscrew/arch-arm/backtrace-arm.c */
+typedef ssize_t (*t_unwind_backtrace_signal_arch)
+(siginfo_t* si, void* sc, const map_info_t* lst, backtrace_frame_t* bt,
+size_t ignore_depth, size_t max_depth);
+typedef map_info_t* (*t_acquire_my_map_info_list)();
+typedef void (*t_release_my_map_info_list)(map_info_t* milist);
+typedef void (*t_get_backtrace_symbols)(const backtrace_frame_t* backtrace,
+ size_t frames,
+ backtrace_symbol_t* symbols);
+typedef void (*t_free_backtrace_symbols)(backtrace_symbol_t* symbols,
+ size_t frames);
+#endif
+
+#endif
+
+/* Process-wide crash handler structure. */
+typedef struct native_code_global_struct {
+ /* Initialized. */
+ int initialized;
+
+ /* Lock. */
+ pthread_mutex_t mutex;
+
+ /* Backup of sigaction. */
+ struct sigaction *sa_old;
+} native_code_global_struct;
+#define NATIVE_CODE_GLOBAL_INITIALIZER { 0, PTHREAD_MUTEX_INITIALIZER, NULL }
+
+/* Thread-specific crash handler structure. */
+typedef struct native_code_handler_struct {
+ /* Restore point context. */
+ sigjmp_buf ctx;
+ int ctx_is_set;
+
+ /* Alternate stack. */
+ char *stack_buffer;
+ size_t stack_buffer_size;
+ stack_t stack_old;
+
+ /* Signal code and info. */
+ int code;
+ siginfo_t si;
+ ucontext_t uc;
+
+ /* Uwind context. */
+#if (defined(USE_CORKSCREW))
+ backtrace_frame_t frames[BACKTRACE_FRAMES_MAX];
+#elif (defined(USE_UNWIND))
+ uintptr_t frames[BACKTRACE_FRAMES_MAX];
+#endif
+ size_t frames_size;
+ size_t frames_skip;
+
+ /* Custom assertion failures. */
+ const char *expression;
+ const char *file;
+ int line;
+
+ /* Alarm was fired. */
+ int alarm;
+} native_code_handler_struct;
+
+/* Global crash handler structure. */
+static native_code_global_struct native_code_g =
+ NATIVE_CODE_GLOBAL_INITIALIZER;
+
+/* Thread variable holding context. */
+pthread_key_t native_code_thread;
+
+#if (defined(USE_UNWIND) && !defined(USE_CORKSCREW))
+/* Unwind callback */
+static _Unwind_Reason_Code
+coffeecatch_unwind_callback(struct _Unwind_Context* context, void* arg) {
+ native_code_handler_struct *const s = (native_code_handler_struct*) arg;
+
+ const uintptr_t ip = _Unwind_GetIP(context);
+
+ DEBUG(print("called unwind callback\n"));
+
+ if (ip != 0x0) {
+ if (s->frames_skip == 0) {
+ s->frames[s->frames_size] = ip;
+ s->frames_size++;
+ } else {
+ s->frames_skip--;
+ }
+ }
+
+ if (s->frames_size == BACKTRACE_FRAMES_MAX) {
+ return _URC_END_OF_STACK;
+ } else {
+ DEBUG(print("returned _URC_OK\n"));
+ return _URC_OK;
+ }
+}
+#endif
+
+/* Use libcorkscrew to get a backtrace inside a signal handler.
+ Will only return a non-zero code on Android >= 4 (with libcorkscrew.so
+ being shipped) */
+#ifdef USE_CORKSCREW
+static ssize_t coffeecatch_backtrace_signal(siginfo_t* si, void* sc,
+ backtrace_frame_t* frames,
+ size_t ignore_depth,
+ size_t max_depth) {
+ void *const libcorkscrew = dlopen("libcorkscrew.so", RTLD_LAZY | RTLD_LOCAL);
+ if (libcorkscrew != NULL) {
+ t_unwind_backtrace_signal_arch unwind_backtrace_signal_arch
+ = (t_unwind_backtrace_signal_arch)
+ dlsym(libcorkscrew, "unwind_backtrace_signal_arch");
+ t_acquire_my_map_info_list acquire_my_map_info_list
+ = (t_acquire_my_map_info_list)
+ dlsym(libcorkscrew, "acquire_my_map_info_list");
+ t_release_my_map_info_list release_my_map_info_list
+ = (t_release_my_map_info_list)
+ dlsym(libcorkscrew, "release_my_map_info_list");
+ if (unwind_backtrace_signal_arch != NULL
+ && acquire_my_map_info_list != NULL
+ && release_my_map_info_list != NULL) {
+ map_info_t*const info = acquire_my_map_info_list();
+ const ssize_t size =
+ unwind_backtrace_signal_arch(si, sc, info, frames, ignore_depth,
+ max_depth);
+ release_my_map_info_list(info);
+ return size;
+ } else {
+ DEBUG(print("symbols not founs in libcorkscrew.so\n"));
+ }
+ dlclose(libcorkscrew);
+ } else {
+ DEBUG(print("libcorkscrew.so could not be loaded\n"));
+ }
+ return -1;
+}
+
+static void coffeecatch_backtrace_symbols(const backtrace_frame_t* backtrace,
+ size_t frames,
+ void (*fun)(void *arg,
+ const backtrace_symbol_t *sym),
+ void *arg) {
+ void *const libcorkscrew = dlopen("libcorkscrew.so", RTLD_LAZY | RTLD_LOCAL);
+ if (libcorkscrew != NULL) {
+ t_get_backtrace_symbols get_backtrace_symbols
+ = (t_get_backtrace_symbols)
+ dlsym(libcorkscrew, "get_backtrace_symbols");
+ t_free_backtrace_symbols free_backtrace_symbols
+ = (t_free_backtrace_symbols)
+ dlsym(libcorkscrew, "free_backtrace_symbols");
+ if (get_backtrace_symbols != NULL
+ && free_backtrace_symbols != NULL) {
+ backtrace_symbol_t symbols[BACKTRACE_FRAMES_MAX];
+ size_t i;
+ if (frames > BACKTRACE_FRAMES_MAX) {
+ frames = BACKTRACE_FRAMES_MAX;
+ }
+ get_backtrace_symbols(backtrace, frames, symbols);
+ for(i = 0; i < frames; i++) {
+ fun(arg, &symbols[i]);
+ }
+ free_backtrace_symbols(symbols, frames);
+ }
+ dlclose(libcorkscrew);
+ }
+}
+#endif
+
+/* Call the old handler. */
+static void coffeecatch_call_old_signal_handler(const int code, siginfo_t *const si,
+ void * const sc) {
+ /* Call the "real" Java handler for JIT and internals. */
+ if (code >= 0 && code < SIG_NUMBER_MAX) {
+ if (native_code_g.sa_old[code].sa_sigaction != NULL) {
+ native_code_g.sa_old[code].sa_sigaction(code, si, sc);
+ } else if (native_code_g.sa_old[code].sa_handler != NULL) {
+ native_code_g.sa_old[code].sa_handler(code);
+ }
+ }
+}
+
+/* Unflag "on stack" */
+static void coffeecatch_revert_alternate_stack(void) {
+#ifndef NO_USE_SIGALTSTACK
+ stack_t ss;
+ if (sigaltstack(NULL, &ss) == 0) {
+ ss.ss_flags &= ~SS_ONSTACK;
+ sigaltstack (&ss, NULL);
+ }
+#endif
+}
+
+/* Try to jump to userland. */
+static void coffeecatch_try_jump_userland(native_code_handler_struct*
+ const t,
+ const int code,
+ siginfo_t *const si,
+ void * const sc) {
+ (void) si; /* UNUSED */
+ (void) sc; /* UNUSED */
+
+ /* Valid context ? */
+ if (t != NULL && t->ctx_is_set) {
+ DEBUG(print("calling siglongjmp()\n"));
+
+ /* Invalidate the context */
+ t->ctx_is_set = 0;
+
+ /* We need to revert the alternate stack before jumping. */
+ coffeecatch_revert_alternate_stack();
+
+ /*
+ * Note on async-signal-safety of siglongjmp() [POSIX] :
+ * "Note that longjmp() and siglongjmp() are not in the list of
+ * async-signal-safe functions. This is because the code executing after
+ * longjmp() and siglongjmp() can call any unsafe functions with the same
+ * danger as calling those unsafe functions directly from the signal
+ * handler. Applications that use longjmp() and siglongjmp() from within
+ * signal handlers require rigorous protection in order to be portable.
+ * Many of the other functions that are excluded from the list are
+ * traditionally implemented using either malloc() or free() functions or
+ * the standard I/O library, both of which traditionally use data
+ * structures in a non-async-signal-safe manner. Since any combination of
+ * different functions using a common data structure can cause
+ * async-signal-safety problems, this volume of POSIX.1-2008 does not
+ * define the behavior when any unsafe function is called in a signal
+ * handler that interrupts an unsafe function."
+ */
+ siglongjmp(t->ctx, code);
+ }
+}
+
+static void coffeecatch_start_alarm(void) {
+ /* Ensure we do not deadlock. Default of ALRM is to die.
+ * (signal() and alarm() are signal-safe) */
+ (void) alarm(30);
+}
+
+static void coffeecatch_mark_alarm(native_code_handler_struct *const t) {
+ t->alarm = 1;
+}
+
+/* Copy context infos (signal code, etc.) */
+static void coffeecatch_copy_context(native_code_handler_struct *const t,
+ const int code, siginfo_t *const si,
+ void *const sc) {
+ t->code = code;
+ t->si = *si;
+ if (sc != NULL) {
+ ucontext_t *const uc = (ucontext_t*) sc;
+ t->uc = *uc;
+ } else {
+ memset(&t->uc, 0, sizeof(t->uc));
+ }
+
+#ifdef USE_UNWIND
+ /* Frame buffer initial position. */
+ t->frames_size = 0;
+
+ /* Skip us and the caller. */
+ t->frames_skip = 2;
+
+ /* Use the corkscrew library to extract the backtrace. */
+#ifdef USE_CORKSCREW
+ t->frames_size = coffeecatch_backtrace_signal(si, sc, t->frames, 0,
+ BACKTRACE_FRAMES_MAX);
+#else
+ /* Unwind frames (equivalent to backtrace()) */
+ _Unwind_Backtrace(coffeecatch_unwind_callback, t);
+#endif
+
+ if (t->frames_size != 0) {
+ DEBUG(print("called _Unwind_Backtrace()\n"));
+ } else {
+ DEBUG(print("called _Unwind_Backtrace(), but no traces\n"));
+ }
+#endif
+}
+
+/* Return the thread-specific native_code_handler_struct structure, or
+ * @c null if no such structure is available. */
+static native_code_handler_struct* coffeecatch_get() {
+ return (native_code_handler_struct*)
+ pthread_getspecific(native_code_thread);
+}
+
+int coffeecatch_cancel_pending_alarm() {
+ native_code_handler_struct *const t = coffeecatch_get();
+ if (t != NULL && t->alarm) {
+ t->alarm = 0;
+ /* "If seconds is 0, a pending alarm request, if any, is canceled." */
+ alarm(0);
+ return 0;
+ }
+ return -1;
+}
+
+/* Internal signal pass-through. Allows to peek the "real" crash before
+ * calling the Java handler. Remember than Java needs many of the signals
+ * (for the JIT, for test-free NullPointerException handling, etc.)
+ * We record the siginfo_t context in this function each time it is being
+ * called, to be able to know what error caused an issue.
+ */
+static void coffeecatch_signal_pass(const int code, siginfo_t *const si,
+ void *const sc) {
+ native_code_handler_struct *t;
+
+ DEBUG(print("caught signal\n"));
+
+ /* Call the "real" Java handler for JIT and internals. */
+ coffeecatch_call_old_signal_handler(code, si, sc);
+
+ /* Still here ?
+ * FIXME TODO: This is the Dalvik behavior - but is it the SunJVM one ? */
+
+ /* Ensure we do not deadlock. Default of ALRM is to die.
+ * (signal() and alarm() are signal-safe) */
+ signal(code, SIG_DFL);
+ coffeecatch_start_alarm();
+
+ /* Available context ? */
+ t = coffeecatch_get();
+ if (t != NULL) {
+ /* An alarm() call was triggered. */
+ coffeecatch_mark_alarm(t);
+
+ /* Take note of the signal. */
+ coffeecatch_copy_context(t, code, si, sc);
+
+ /* Back to the future. */
+ coffeecatch_try_jump_userland(t, code, si, sc);
+ }
+
+ /* Nope. (abort() is signal-safe) */
+ DEBUG(print("calling abort()\n"));
+ signal(SIGABRT, SIG_DFL);
+ abort();
+}
+
+/* Internal crash handler for abort(). Java calls abort() if its signal handler
+ * could not resolve the signal ; thus calling us through this handler. */
+static void coffeecatch_signal_abort(const int code, siginfo_t *const si,
+ void *const sc) {
+ native_code_handler_struct *t;
+
+ (void) sc; /* UNUSED */
+
+ DEBUG(print("caught abort\n"));
+
+ /* Ensure we do not deadlock. Default of ALRM is to die.
+ * (signal() and alarm() are signal-safe) */
+ signal(code, SIG_DFL);
+ coffeecatch_start_alarm();
+
+ /* Available context ? */
+ t = coffeecatch_get();
+ if (t != NULL) {
+ /* An alarm() call was triggered. */
+ coffeecatch_mark_alarm(t);
+
+ /* Take note (real "abort()") */
+ coffeecatch_copy_context(t, code, si, sc);
+
+ /* Back to the future. */
+ coffeecatch_try_jump_userland(t, code, si, sc);
+ }
+
+ /* No such restore point, call old signal handler then. */
+ DEBUG(print("calling old signal handler\n"));
+ coffeecatch_call_old_signal_handler(code, si, sc);
+
+ /* Nope. (abort() is signal-safe) */
+ DEBUG(print("calling abort()\n"));
+ abort();
+}
+
+/* Internal globals initialization. */
+static int coffeecatch_handler_setup_global(void) {
+ if (native_code_g.initialized++ == 0) {
+ size_t i;
+ struct sigaction sa_abort;
+ struct sigaction sa_pass;
+
+ DEBUG(print("installing global signal handlers\n"));
+
+ /* Setup handler structure. */
+ memset(&sa_abort, 0, sizeof(sa_abort));
+ sigemptyset(&sa_abort.sa_mask);
+ sa_abort.sa_sigaction = coffeecatch_signal_abort;
+ sa_abort.sa_flags = SA_SIGINFO | SA_ONSTACK;
+
+ memset(&sa_pass, 0, sizeof(sa_pass));
+ sigemptyset(&sa_pass.sa_mask);
+ sa_pass.sa_sigaction = coffeecatch_signal_pass;
+ sa_pass.sa_flags = SA_SIGINFO | SA_ONSTACK;
+
+ /* Allocate */
+ native_code_g.sa_old = calloc(sizeof(struct sigaction), SIG_NUMBER_MAX);
+ if (native_code_g.sa_old == NULL) {
+ return -1;
+ }
+
+ /* Setup signal handlers for SIGABRT (Java calls abort()) and others. **/
+ for (i = 0; native_sig_catch[i] != 0; i++) {
+ const int sig = native_sig_catch[i];
+ const struct sigaction * const action =
+ sig == SIGABRT ? &sa_abort : &sa_pass;
+ assert(sig < SIG_NUMBER_MAX);
+ if (sigaction(sig, action, &native_code_g.sa_old[sig]) != 0) {
+ return -1;
+ }
+ }
+
+ /* Initialize thread var. */
+ if (pthread_key_create(&native_code_thread, NULL) != 0) {
+ return -1;
+ }
+
+ DEBUG(print("installed global signal handlers\n"));
+ }
+
+ /* OK. */
+ return 0;
+}
+
+/**
+ * Free a native_code_handler_struct structure.
+ **/
+static int coffeecatch_native_code_handler_struct_free(native_code_handler_struct *const t) {
+ int code = 0;
+
+ if (t == NULL) {
+ return -1;
+ }
+
+#ifndef NO_USE_SIGALTSTACK
+ /* Restore previous alternative stack. */
+ if (t->stack_old.ss_sp != NULL && sigaltstack(&t->stack_old, NULL) != 0) {
+#ifndef USE_SILENT_SIGALTSTACK
+ code = -1;
+#endif
+ }
+#endif
+
+ /* Free alternative stack */
+ if (t->stack_buffer != NULL) {
+ free(t->stack_buffer);
+ t->stack_buffer = NULL;
+ t->stack_buffer_size = 0;
+ }
+
+ /* Free structure. */
+ free(t);
+
+ return code;
+}
+
+/**
+ * Create a native_code_handler_struct structure.
+ **/
+static native_code_handler_struct* coffeecatch_native_code_handler_struct_init(void) {
+ stack_t stack;
+ native_code_handler_struct *const t =
+ calloc(sizeof(native_code_handler_struct), 1);
+
+ if (t == NULL) {
+ return NULL;
+ }
+
+ DEBUG(print("installing thread alternative stack\n"));
+
+ /* Initialize structure */
+ t->stack_buffer_size = SIG_STACK_BUFFER_SIZE;
+ t->stack_buffer = malloc(t->stack_buffer_size);
+ if (t->stack_buffer == NULL) {
+ coffeecatch_native_code_handler_struct_free(t);
+ return NULL;
+ }
+
+ /* Setup alternative stack. */
+ memset(&stack, 0, sizeof(stack));
+ stack.ss_sp = t->stack_buffer;
+ stack.ss_size = t->stack_buffer_size;
+ stack.ss_flags = 0;
+
+#ifndef NO_USE_SIGALTSTACK
+ /* Install alternative stack. This is thread-safe */
+ if (sigaltstack(&stack, &t->stack_old) != 0) {
+#ifndef USE_SILENT_SIGALTSTACK
+ coffeecatch_native_code_handler_struct_free(t);
+ return NULL;
+#endif
+ }
+#endif
+
+ return t;
+}
+
+/**
+ * Acquire the crash handler for the current thread.
+ * The coffeecatch_handler_cleanup() must be called to release allocated
+ * resources.
+ **/
+static int coffeecatch_handler_setup(int setup_thread) {
+ int code;
+
+ DEBUG(print("setup for a new handler\n"));
+
+ /* Initialize globals. */
+ if (pthread_mutex_lock(&native_code_g.mutex) != 0) {
+ return -1;
+ }
+ code = coffeecatch_handler_setup_global();
+ if (pthread_mutex_unlock(&native_code_g.mutex) != 0) {
+ return -1;
+ }
+
+ /* Global initialization failed. */
+ if (code != 0) {
+ return -1;
+ }
+
+ /* Initialize locals. */
+ if (setup_thread && coffeecatch_get() == NULL) {
+ native_code_handler_struct *const t =
+ coffeecatch_native_code_handler_struct_init();
+
+ if (t == NULL) {
+ return -1;
+ }
+
+ DEBUG(print("installing thread alternative stack\n"));
+
+ /* Set thread-specific value. */
+ if (pthread_setspecific(native_code_thread, t) != 0) {
+ coffeecatch_native_code_handler_struct_free(t);
+ return -1;
+ }
+
+ DEBUG(print("installed thread alternative stack\n"));
+ }
+
+ /* OK. */
+ return 0;
+}
+
+/**
+ * Release the resources allocated by a previous call to
+ * coffeecatch_handler_setup().
+ * This function must be called as many times as
+ * coffeecatch_handler_setup() was called to fully release allocated
+ * resources.
+ **/
+static int coffeecatch_handler_cleanup() {
+ /* Cleanup locals. */
+ native_code_handler_struct *const t = coffeecatch_get();
+ if (t != NULL) {
+ DEBUG(print("removing thread alternative stack\n"));
+
+ /* Erase thread-specific value now (detach). */
+ if (pthread_setspecific(native_code_thread, NULL) != 0) {
+ assert(! "pthread_setspecific() failed");
+ }
+
+ /* Free handler and reset slternate stack */
+ if (coffeecatch_native_code_handler_struct_free(t) != 0) {
+ return -1;
+ }
+
+ DEBUG(print("removed thread alternative stack\n"));
+ }
+
+ /* Cleanup globals. */
+ if (pthread_mutex_lock(&native_code_g.mutex) != 0) {
+ assert(! "pthread_mutex_lock() failed");
+ }
+ assert(native_code_g.initialized != 0);
+ if (--native_code_g.initialized == 0) {
+ size_t i;
+
+ DEBUG(print("removing global signal handlers\n"));
+
+ /* Restore signal handler. */
+ for(i = 0; native_sig_catch[i] != 0; i++) {
+ const int sig = native_sig_catch[i];
+ assert(sig < SIG_NUMBER_MAX);
+ if (sigaction(sig, &native_code_g.sa_old[sig], NULL) != 0) {
+ return -1;
+ }
+ }
+
+ /* Free old structure. */
+ free(native_code_g.sa_old);
+ native_code_g.sa_old = NULL;
+
+ /* Delete thread var. */
+ if (pthread_key_delete(native_code_thread) != 0) {
+ assert(! "pthread_key_delete() failed");
+ }
+
+ DEBUG(print("removed global signal handlers\n"));
+ }
+ if (pthread_mutex_unlock(&native_code_g.mutex) != 0) {
+ assert(! "pthread_mutex_unlock() failed");
+ }
+
+ return 0;
+}
+
+/**
+ * Get the signal associated with the crash.
+ */
+int coffeecatch_get_signal() {
+ const native_code_handler_struct* const t = coffeecatch_get();
+ if (t != NULL) {
+ return t->code;
+ } else {
+ return -1;
+ }
+}
+
+/* Signal descriptions.
+ See <http://pubs.opengroup.org/onlinepubs/009696699/basedefs/signal.h.html>
+*/
+static const char* coffeecatch_desc_sig(int sig, int code) {
+ switch(sig) {
+ case SIGILL:
+ switch(code) {
+ case ILL_ILLOPC:
+ return "Illegal opcode";
+ case ILL_ILLOPN:
+ return "Illegal operand";
+ case ILL_ILLADR:
+ return "Illegal addressing mode";
+ case ILL_ILLTRP:
+ return "Illegal trap";
+ case ILL_PRVOPC:
+ return "Privileged opcode";
+ case ILL_PRVREG:
+ return "Privileged register";
+ case ILL_COPROC:
+ return "Coprocessor error";
+ case ILL_BADSTK:
+ return "Internal stack error";
+ default:
+ return "Illegal operation";
+ }
+ break;
+ case SIGFPE:
+ switch(code) {
+ case FPE_INTDIV:
+ return "Integer divide by zero";
+ case FPE_INTOVF:
+ return "Integer overflow";
+ case FPE_FLTDIV:
+ return "Floating-point divide by zero";
+ case FPE_FLTOVF:
+ return "Floating-point overflow";
+ case FPE_FLTUND:
+ return "Floating-point underflow";
+ case FPE_FLTRES:
+ return "Floating-point inexact result";
+ case FPE_FLTINV:
+ return "Invalid floating-point operation";
+ case FPE_FLTSUB:
+ return "Subscript out of range";
+ default:
+ return "Floating-point";
+ }
+ break;
+ case SIGSEGV:
+ switch(code) {
+ case SEGV_MAPERR:
+ return "Address not mapped to object";
+ case SEGV_ACCERR:
+ return "Invalid permissions for mapped object";
+ default:
+ return "Segmentation violation";
+ }
+ break;
+ case SIGBUS:
+ switch(code) {
+ case BUS_ADRALN:
+ return "Invalid address alignment";
+ case BUS_ADRERR:
+ return "Nonexistent physical address";
+ case BUS_OBJERR:
+ return "Object-specific hardware error";
+ default:
+ return "Bus error";
+ }
+ break;
+ case SIGTRAP:
+ switch(code) {
+ case TRAP_BRKPT:
+ return "Process breakpoint";
+ case TRAP_TRACE:
+ return "Process trace trap";
+ default:
+ return "Trap";
+ }
+ break;
+ case SIGCHLD:
+ switch(code) {
+ case CLD_EXITED:
+ return "Child has exited";
+ case CLD_KILLED:
+ return "Child has terminated abnormally and did not create a core file";
+ case CLD_DUMPED:
+ return "Child has terminated abnormally and created a core file";
+ case CLD_TRAPPED:
+ return "Traced child has trapped";
+ case CLD_STOPPED:
+ return "Child has stopped";
+ case CLD_CONTINUED:
+ return "Stopped child has continued";
+ default:
+ return "Child";
+ }
+ break;
+ case SIGPOLL:
+ switch(code) {
+ case POLL_IN:
+ return "Data input available";
+ case POLL_OUT:
+ return "Output buffers available";
+ case POLL_MSG:
+ return "Input message available";
+ case POLL_ERR:
+ return "I/O error";
+ case POLL_PRI:
+ return "High priority input available";
+ case POLL_HUP:
+ return "Device disconnected";
+ default:
+ return "Pool";
+ }
+ break;
+ case SIGABRT:
+ return "Process abort signal";
+ case SIGALRM:
+ return "Alarm clock";
+ case SIGCONT:
+ return "Continue executing, if stopped";
+ case SIGHUP:
+ return "Hangup";
+ case SIGINT:
+ return "Terminal interrupt signal";
+ case SIGKILL:
+ return "Kill";
+ case SIGPIPE:
+ return "Write on a pipe with no one to read it";
+ case SIGQUIT:
+ return "Terminal quit signal";
+ case SIGSTOP:
+ return "Stop executing";
+ case SIGTERM:
+ return "Termination signal";
+ case SIGTSTP:
+ return "Terminal stop signal";
+ case SIGTTIN:
+ return "Background process attempting read";
+ case SIGTTOU:
+ return "Background process attempting write";
+ case SIGUSR1:
+ return "User-defined signal 1";
+ case SIGUSR2:
+ return "User-defined signal 2";
+ case SIGPROF:
+ return "Profiling timer expired";
+ case SIGSYS:
+ return "Bad system call";
+ case SIGVTALRM:
+ return "Virtual timer expired";
+ case SIGURG:
+ return "High bandwidth data is available at a socket";
+ case SIGXCPU:
+ return "CPU time limit exceeded";
+ case SIGXFSZ:
+ return "File size limit exceeded";
+ default:
+ switch(code) {
+ case SI_USER:
+ return "Signal sent by kill()";
+ case SI_QUEUE:
+ return "Signal sent by the sigqueue()";
+ case SI_TIMER:
+ return "Signal generated by expiration of a timer set by timer_settime()";
+ case SI_ASYNCIO:
+ return "Signal generated by completion of an asynchronous I/O request";
+ case SI_MESGQ:
+ return
+ "Signal generated by arrival of a message on an empty message queue";
+ default:
+ return "Unknown signal";
+ }
+ break;
+ }
+}
+
+/**
+ * Get the backtrace size. Returns 0 if no backtrace is available.
+ */
+size_t coffeecatch_get_backtrace_size(void) {
+#ifdef USE_UNWIND
+ const native_code_handler_struct* const t = coffeecatch_get();
+ if (t != NULL) {
+ return t->frames_size;
+ } else {
+ return 0;
+ }
+#else
+ return 0;
+#endif
+}
+
+/**
+ * Get the <index>th element of the backtrace, or 0 upon error.
+ */
+uintptr_t coffeecatch_get_backtrace(ssize_t index) {
+#ifdef USE_UNWIND
+ const native_code_handler_struct* const t = coffeecatch_get();
+ if (t != NULL) {
+ if (index < 0) {
+ index = t->frames_size + index;
+ }
+ if (index >= 0 && (size_t) index < t->frames_size) {
+#ifdef USE_CORKSCREW
+ return t->frames[index].absolute_pc;
+#else
+ return t->frames[index];
+#endif
+ }
+ }
+#else
+ (void) index;
+#endif
+ return 0;
+}
+
+/**
+ * Get the program counter, given a pointer to a ucontext_t context.
+ **/
+static uintptr_t coffeecatch_get_pc_from_ucontext(const ucontext_t *uc) {
+#if (defined(__arm__))
+ return uc->uc_mcontext.arm_pc;
+#elif (defined(__x86_64__))
+ return uc->uc_mcontext.gregs[REG_RIP];
+#elif (defined(__i386))
+ return uc->uc_mcontext.gregs[REG_EIP];
+#elif (defined (__ppc__)) || (defined (__powerpc__))
+ return uc->uc_mcontext.regs->nip;
+#elif (defined(__hppa__))
+ return uc->uc_mcontext.sc_iaoq[0] & ~0x3UL;
+#elif (defined(__sparc__) && defined (__arch64__))
+ return uc->uc_mcontext.mc_gregs[MC_PC];
+#elif (defined(__sparc__) && !defined (__arch64__))
+ return uc->uc_mcontext.gregs[REG_PC];
+#elif (defined(__mips__))
+ return uc->uc_mcontext.gregs[31];
+#else
+#error "Architecture is unknown, please report me!"
+#endif
+}
+
+/* Is this module name look like a DLL ?
+ FIXME: find a better way to do that... */
+static int coffeecatch_is_dll(const char *name) {
+ size_t i;
+ for(i = 0; name[i] != '\0'; i++) {
+ if (name[i + 0] == '.' &&
+ name[i + 1] == 's' &&
+ name[i + 2] == 'o' &&
+ ( name[i + 3] == '\0' || name[i + 3] == '.') ) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Extract a line information on a PC address. */
+static void format_pc_address_cb(uintptr_t pc,
+ void (*fun)(void *arg, const char *module,
+ uintptr_t addr,
+ const char *function,
+ uintptr_t offset), void *arg) {
+ if (pc != 0) {
+ Dl_info info;
+ void * const addr = (void*) pc;
+ /* dladdr() returns 0 on error, and nonzero on success. */
+ if (dladdr(addr, &info) != 0 && info.dli_fname != NULL) {
+ const uintptr_t near = (uintptr_t) info.dli_saddr;
+ const uintptr_t offs = pc - near;
+ const uintptr_t addr_rel = pc - (uintptr_t) info.dli_fbase;
+ /* We need the absolute address for the main module (?).
+ TODO FIXME to be investigated. */
+ const uintptr_t addr_to_use = coffeecatch_is_dll(info.dli_fname)
+ ? addr_rel : pc;
+ fun(arg, info.dli_fname, addr_to_use, info.dli_sname, offs);
+ } else {
+ fun(arg, NULL, pc, NULL, 0);
+ }
+ }
+}
+
+typedef struct t_print_fun {
+ char *buffer;
+ size_t buffer_size;
+} t_print_fun;
+
+static void print_fun(void *arg, const char *module, uintptr_t uaddr,
+ const char *function, uintptr_t offset) {
+ t_print_fun *const t = (t_print_fun*) arg;
+ char *const buffer = t->buffer;
+ const size_t buffer_size = t->buffer_size;
+ const void*const addr = (void*) uaddr;
+ if (module == NULL) {
+ snprintf(buffer, buffer_size, "[at %p]", addr);
+ } else if (function != NULL) {
+ snprintf(buffer, buffer_size, "[at %s:%p (%s+0x%x)]", module, addr,
+ function, (int) offset);
+ } else {
+ snprintf(buffer, buffer_size, "[at %s:%p]", module, addr);
+ }
+}
+
+/* Format a line information on a PC address. */
+static void format_pc_address(char *buffer, size_t buffer_size, uintptr_t pc) {
+ t_print_fun t;
+ t.buffer = buffer;
+ t.buffer_size = buffer_size;
+ format_pc_address_cb(pc, print_fun, &t);
+}
+
+/**
+ * Get the full error message associated with the crash.
+ */
+const char* coffeecatch_get_message() {
+ const int error = errno;
+ const native_code_handler_struct* const t = coffeecatch_get();
+
+ /* Found valid handler. */
+ if (t != NULL) {
+ char * const buffer = t->stack_buffer;
+ const size_t buffer_len = t->stack_buffer_size;
+ size_t buffer_offs = 0;
+
+ const char* const posix_desc =
+ coffeecatch_desc_sig(t->si.si_signo, t->si.si_code);
+
+ /* Assertion failure ? */
+ if ((t->code == SIGABRT
+#ifdef __ANDROID__
+ /* See Android BUG #16672:
+ * "C assert() failure causes SIGSEGV when it should cause SIGABRT" */
+ || (t->code == SIGSEGV && (uintptr_t) t->si.si_addr == 0xdeadbaad)
+#endif
+ ) && t->expression != NULL) {
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs,
+ "assertion '%s' failed at %s:%d",
+ t->expression, t->file, t->line);
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ }
+ /* Signal */
+ else {
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, "signal %d",
+ t->si.si_signo);
+ buffer_offs += strlen(&buffer[buffer_offs]);
+
+ /* Description */
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, " (%s)",
+ posix_desc);
+ buffer_offs += strlen(&buffer[buffer_offs]);
+
+ /* Address of faulting instruction */
+ if (t->si.si_signo == SIGILL || t->si.si_signo == SIGSEGV) {
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, " at address %p",
+ t->si.si_addr);
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ }
+ }
+
+ /* [POSIX] If non-zero, an errno value associated with this signal,
+ as defined in <errno.h>. */
+ if (t->si.si_errno != 0) {
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, ": ");
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ if (strerror_r(t->si.si_errno, &buffer[buffer_offs],
+ buffer_len - buffer_offs) == 0) {
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs,
+ "unknown error");
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ }
+ }
+
+ /* Sending process ID. */
+ if (t->si.si_signo == SIGCHLD && t->si.si_pid != 0) {
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs,
+ " (sent by pid %d)", (int) t->si.si_pid);
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ }
+
+ /* Faulting program counter location. */
+ if (coffeecatch_get_pc_from_ucontext(&t->uc) != 0) {
+ const uintptr_t pc = coffeecatch_get_pc_from_ucontext(&t->uc);
+ snprintf(&buffer[buffer_offs], buffer_len - buffer_offs, " ");
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ format_pc_address(&buffer[buffer_offs], buffer_len - buffer_offs, pc);
+ buffer_offs += strlen(&buffer[buffer_offs]);
+ }
+
+ /* Return string. */
+ buffer[buffer_offs] = '\0';
+ return t->stack_buffer;
+ } else {
+ /* Static buffer in case of emergency */
+ static char buffer[256];
+#ifdef _GNU_SOURCE
+ return strerror_r(error, &buffer[0], sizeof(buffer));
+#else
+ const int code = strerror_r(error, &buffer[0], sizeof(buffer));
+ errno = error;
+ if (code == 0) {
+ return buffer;
+ } else {
+ return "unknown error during crash handler setup";
+ }
+#endif
+ }
+}
+
+#if (defined(USE_CORKSCREW))
+typedef struct t_coffeecatch_backtrace_symbols_fun {
+ void (*fun)(void *arg, const char *module, uintptr_t addr,
+ const char *function, uintptr_t offset);
+ void *arg;
+} t_coffeecatch_backtrace_symbols_fun;
+
+static void coffeecatch_backtrace_symbols_fun(void *arg, const backtrace_symbol_t *sym) {
+ t_coffeecatch_backtrace_symbols_fun *const bt =
+ (t_coffeecatch_backtrace_symbols_fun*) arg;
+ const char *symbol = sym->demangled_name != NULL
+ ? sym->demangled_name : sym->symbol_name;
+ const uintptr_t rel = sym->relative_pc - sym->relative_symbol_addr;
+ bt->fun(bt->arg, sym->map_name, sym->relative_pc, symbol, rel);
+}
+#endif
+
+/**
+ * Enumerate backtrace information.
+ */
+void coffeecatch_get_backtrace_info(void (*fun)(void *arg,
+ const char *module,
+ uintptr_t addr,
+ const char *function,
+ uintptr_t offset), void *arg) {
+ const native_code_handler_struct* const t = coffeecatch_get();
+ if (t != NULL) {
+#if (defined(USE_CORKSCREW))
+ t_coffeecatch_backtrace_symbols_fun bt;
+ bt.fun = fun;
+ bt.arg = arg;
+ coffeecatch_backtrace_symbols(t->frames, t->frames_size,
+ coffeecatch_backtrace_symbols_fun, &bt);
+#elif (defined(USE_UNWIND))
+ size_t i;
+ for(i = 0; i < t->frames_size; i++) {
+ const uintptr_t pc = t->frames[i];
+ format_pc_address_cb(pc, fun, arg);
+ }
+#else
+ (void) fun;
+ (void) arg;
+#endif
+ }
+}
+
+/**
+ * Calls coffeecatch_handler_setup(1) to setup a crash handler, mark the
+ * context as valid, and return 0 upon success.
+ */
+int coffeecatch_setup() {
+ if (coffeecatch_handler_setup(1) == 0) {
+ native_code_handler_struct *const t = coffeecatch_get();
+ assert(t != NULL);
+ t->ctx_is_set = 1;
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+/**
+ * Calls coffeecatch_handler_cleanup()
+ */
+void coffeecatch_cleanup() {
+ native_code_handler_struct *const t = coffeecatch_get();
+ assert(t != NULL);
+ t->ctx_is_set = 0;
+ coffeecatch_handler_cleanup();
+}
+
+sigjmp_buf* coffeecatch_get_ctx() {
+ native_code_handler_struct* t = coffeecatch_get();
+ assert(t != NULL);
+ return &t->ctx;
+}
+
+void coffeecatch_abort(const char* exp, const char* file, int line) {
+ native_code_handler_struct *const t = coffeecatch_get();
+ if (t != NULL) {
+ t->expression = exp;
+ t->file = file;
+ t->line = line;
+ }
+ abort();
+}
diff --git a/android/cpp/coffeecatch_r10c.diff b/android/cpp/coffeecatch_r10c.diff
new file mode 100644
index 0000000000..ddee5b3292
--- /dev/null
+++ b/android/cpp/coffeecatch_r10c.diff
@@ -0,0 +1,216 @@
+diff --git a/coffeecatch.c b/coffeecatch.c
+index 448fe0f..f39fcdb 100644
+--- a/coffeecatch.c
++++ b/coffeecatch.c
+@@ -43,10 +43,7 @@
+ #include <assert.h>
+ #include <signal.h>
+ #include <setjmp.h>
+-#if defined(__ANDROID__) && !defined(__BIONIC_HAVE_UCONTEXT_T) && \
+- defined(__arm__) && !defined(__BIONIC_HAVE_STRUCT_SIGCONTEXT)
+-#include <asm/sigcontext.h>
+-#endif
++#include <sys/ucontext.h>
+ #if (defined(USE_UNWIND) && !defined(USE_CORKSCREW))
+ #include <unwind.h>
+ #endif
+@@ -88,154 +85,34 @@ static const int native_sig_catch[SIG_CATCH_COUNT + 1]
+ /* Maximum value of a caught signal. */
+ #define SIG_NUMBER_MAX 32
+
+-#if (defined(__ANDROID__) && (!defined(__BIONIC_HAVE_UCONTEXT_T)))
+-#ifndef ucontext_h_seen
+-#define ucontext_h_seen
+-
+-/* stack_t definition */
+-#include <asm/signal.h>
+-
+-#if defined(__arm__)
+-
+-/* Taken from richard.quirk's header file. (Android does not have it) */
+-
+-typedef struct ucontext {
+- unsigned long uc_flags;
+- struct ucontext *uc_link;
+- stack_t uc_stack;
+- struct sigcontext uc_mcontext;
+- unsigned long uc_sigmask;
+-} ucontext_t;
+-
+-#elif defined(__i386__)
+-
+-/* Taken from Google Breakpad. */
+-
+-/* 80-bit floating-point register */
+-struct _libc_fpreg {
+- unsigned short significand[4];
+- unsigned short exponent;
+-};
+-
+-/* Simple floating-point state, see FNSTENV instruction */
+-struct _libc_fpstate {
+- unsigned long cw;
+- unsigned long sw;
+- unsigned long tag;
+- unsigned long ipoff;
+- unsigned long cssel;
+- unsigned long dataoff;
+- unsigned long datasel;
+- struct _libc_fpreg _st[8];
+- unsigned long status;
+-};
+-
+-typedef uint32_t greg_t;
+-
+-typedef struct {
+- uint32_t gregs[19];
+- struct _libc_fpstate* fpregs;
+- uint32_t oldmask;
+- uint32_t cr2;
+-} mcontext_t;
+-
+-enum {
+- REG_GS = 0,
+- REG_FS,
+- REG_ES,
+- REG_DS,
+- REG_EDI,
+- REG_ESI,
+- REG_EBP,
+- REG_ESP,
+- REG_EBX,
+- REG_EDX,
+- REG_ECX,
+- REG_EAX,
+- REG_TRAPNO,
+- REG_ERR,
+- REG_EIP,
+- REG_CS,
+- REG_EFL,
+- REG_UESP,
+- REG_SS,
+-};
+-
+-typedef struct ucontext {
+- uint32_t uc_flags;
+- struct ucontext* uc_link;
+- stack_t uc_stack;
+- mcontext_t uc_mcontext;
+-} ucontext_t;
+-
+-#elif defined(__mips__)
+-
+-/* Taken from Google Breakpad. */
+-
+-typedef struct {
+- uint32_t regmask;
+- uint32_t status;
+- uint64_t pc;
+- uint64_t gregs[32];
+- uint64_t fpregs[32];
+- uint32_t acx;
+- uint32_t fpc_csr;
+- uint32_t fpc_eir;
+- uint32_t used_math;
+- uint32_t dsp;
+- uint64_t mdhi;
+- uint64_t mdlo;
+- uint32_t hi1;
+- uint32_t lo1;
+- uint32_t hi2;
+- uint32_t lo2;
+- uint32_t hi3;
+- uint32_t lo3;
+-} mcontext_t;
+-
+-typedef struct ucontext {
+- uint32_t uc_flags;
+- struct ucontext* uc_link;
+- stack_t uc_stack;
+- mcontext_t uc_mcontext;
+-} ucontext_t;
+-
+-#else
+-#error "Architecture is not supported (unknown ucontext layout)"
+-#endif
+-
+-#endif
+-
+ #ifdef USE_CORKSCREW
+ typedef struct map_info_t map_info_t;
+ /* Extracted from Android's include/corkscrew/backtrace.h */
+-typedef struct {
+- uintptr_t absolute_pc;
+- uintptr_t stack_top;
+- size_t stack_size;
+-} backtrace_frame_t;
+-typedef struct {
+- uintptr_t relative_pc;
+- uintptr_t relative_symbol_addr;
+- char* map_name;
+- char* symbol_name;
+- char* demangled_name;
++typedef struct {
++ uintptr_t absolute_pc;
++ uintptr_t stack_top;
++ size_t stack_size;
++} backtrace_frame_t;
++typedef struct {
++ uintptr_t relative_pc;
++ uintptr_t relative_symbol_addr;
++ char* map_name;
++ char* symbol_name;
++ char* demangled_name;
+ } backtrace_symbol_t;
+ /* Extracted from Android's libcorkscrew/arch-arm/backtrace-arm.c */
+-typedef ssize_t (*t_unwind_backtrace_signal_arch)
+-(siginfo_t* si, void* sc, const map_info_t* lst, backtrace_frame_t* bt,
+-size_t ignore_depth, size_t max_depth);
+-typedef map_info_t* (*t_acquire_my_map_info_list)();
+-typedef void (*t_release_my_map_info_list)(map_info_t* milist);
+-typedef void (*t_get_backtrace_symbols)(const backtrace_frame_t* backtrace,
++typedef ssize_t (*t_unwind_backtrace_signal_arch)
++(siginfo_t* si, void* sc, const map_info_t* lst, backtrace_frame_t* bt,
++size_t ignore_depth, size_t max_depth);
++typedef map_info_t* (*t_acquire_my_map_info_list)();
++typedef void (*t_release_my_map_info_list)(map_info_t* milist);
++typedef void (*t_get_backtrace_symbols)(const backtrace_frame_t* backtrace,
+ size_t frames,
+ backtrace_symbol_t* symbols);
+ typedef void (*t_free_backtrace_symbols)(backtrace_symbol_t* symbols,
+ size_t frames);
+ #endif
+
+-#endif
+-
+ /* Process-wide crash handler structure. */
+ typedef struct native_code_global_struct {
+ /* Initialized. */
+@@ -341,7 +218,7 @@ static ssize_t coffeecatch_backtrace_signal(siginfo_t* si, void* sc,
+ && acquire_my_map_info_list != NULL
+ && release_my_map_info_list != NULL) {
+ map_info_t*const info = acquire_my_map_info_list();
+- const ssize_t size =
++ const ssize_t size =
+ unwind_backtrace_signal_arch(si, sc, info, frames, ignore_depth,
+ max_depth);
+ release_my_map_info_list(info);
+@@ -1073,12 +950,12 @@ static uintptr_t coffeecatch_get_pc_from_ucontext(const ucontext_t *uc) {
+ return uc->uc_mcontext.regs->nip;
+ #elif (defined(__hppa__))
+ return uc->uc_mcontext.sc_iaoq[0] & ~0x3UL;
+-#elif (defined(__sparc__) && defined (__arch64__))
+- return uc->uc_mcontext.mc_gregs[MC_PC];
+-#elif (defined(__sparc__) && !defined (__arch64__))
+- return uc->uc_mcontext.gregs[REG_PC];
+-#elif (defined(__mips__))
+- return uc->uc_mcontext.gregs[31];
++#elif (defined(__sparc__) && defined (__arch64__))
++ return uc->uc_mcontext.mc_gregs[MC_PC];
++#elif (defined(__sparc__) && !defined (__arch64__))
++ return uc->uc_mcontext.gregs[REG_PC];
++#elif (defined(__mips__))
++ return uc->uc_mcontext.gregs[31];
+ #else
+ #error "Architecture is unknown, please report me!"
+ #endif \ No newline at end of file
diff --git a/android/cpp/coffeejni.c b/android/cpp/coffeejni.c
new file mode 100644
index 0000000000..6dc9db5fa4
--- /dev/null
+++ b/android/cpp/coffeejni.c
@@ -0,0 +1,185 @@
+/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.
+ * (especially for Android/Dalvik)
+ *
+ * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. 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 OWNER 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.
+ */
+
+#ifndef COFFEECATCH_JNI_H
+#define COFFEECATCH_JNI_H
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <jni.h>
+#include <assert.h>
+#include <coffeecatch/coffeecatch.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct t_bt_fun {
+ JNIEnv* env;
+ jclass cls;
+ jclass cls_ste;
+ jmethodID cons_ste;
+ jobjectArray elements;
+ size_t size;
+ size_t index;
+} t_bt_fun;
+
+static char* bt_print(const char *function, uintptr_t offset) {
+ if (function != NULL) {
+ char buffer[256];
+ snprintf(buffer, sizeof(buffer), "%s:%p", function, (void*) offset);
+ return strdup(buffer);
+ } else {
+ return "<unknown>";
+ }
+}
+
+static char* bt_addr(uintptr_t addr) {
+ char buffer[32];
+ snprintf(buffer, sizeof(buffer), "%p", (void*) addr);
+ return strdup(buffer);
+}
+
+#define IS_VALID_CLASS_CHAR(C) ( \
+ ((C) >= 'a' && (C) <= 'z') \
+ || ((C) >= 'A' && (C) <= 'Z') \
+ || ((C) >= '0' && (C) <= '9') \
+ || (C) == '_' \
+ )
+
+static char* bt_module(const char *module) {
+ if (module != NULL) {
+ size_t i;
+ char *copy;
+ if (*module == '/') {
+ module++;
+ }
+ copy = strdup(module);
+ /* Pseudo-java-class. */
+ for(i = 0; copy[i] != '\0'; i++) {
+ if (copy[i] == '/') {
+ copy[i] = '.';
+ } else if (!IS_VALID_CLASS_CHAR(copy[i])) {
+ copy[i] = '_';
+ }
+ }
+ return copy;
+ } else {
+ return "<unknown>";
+ }
+}
+
+static void bt_fun(void *arg, const char *module, uintptr_t addr,
+ const char *function, uintptr_t offset) {
+ t_bt_fun *const t = (t_bt_fun*) arg;
+ JNIEnv*const env = t->env;
+ jstring declaringClass = (*env)->NewStringUTF(env, bt_module(module));
+ jstring methodName = (*env)->NewStringUTF(env, bt_addr(addr));
+ jstring fileName = (*env)->NewStringUTF(env, bt_print(function, offset));
+ const int lineNumber = function != NULL ? 0 : -2; /* "-2" is "inside JNI code" */
+ jobject trace = (*env)->NewObject(env, t->cls_ste, t->cons_ste,
+ declaringClass, methodName, fileName,
+ lineNumber);
+ if (t->index < t->size) {
+ (*t->env)->SetObjectArrayElement(t->env, t->elements, t->index++, trace);
+ }
+}
+
+void coffeecatch_throw_exception(JNIEnv* env) {
+ jclass cls = (*env)->FindClass(env, "java/lang/Error");
+ jclass cls_ste = (*env)->FindClass(env, "java/lang/StackTraceElement");
+
+ jmethodID cons = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
+ jmethodID cons_cause = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;Ljava/lang/Throwable;)V");
+ jmethodID cons_ste = (*env)->GetMethodID(env, cls_ste, "<init>",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V");
+ jmethodID meth_sste = (*env)->GetMethodID(env, cls, "setStackTrace",
+ "([Ljava/lang/StackTraceElement;)V");
+
+ /* Exception message. */
+ const char*const message = coffeecatch_get_message();
+ jstring str = (*env)->NewStringUTF(env, strdup(message));
+
+ /* Final exception. */
+ jthrowable exception;
+
+ /* Add pseudo-stack trace. */
+ const ssize_t bt_size = coffeecatch_get_backtrace_size();
+
+ assert(cls != NULL);
+ assert(cls_ste != NULL);
+ assert(cons != NULL);
+ assert(cons_cause != NULL);
+ assert(cons_ste != NULL);
+ assert(meth_sste != NULL);
+
+ assert(message != NULL);
+ assert(str != NULL);
+
+ /* Can we produce a stack trace ? */
+ if (bt_size > 0) {
+ /* Create secondary exception. */
+ jthrowable cause = (jthrowable) (*env)->NewObject(env, cls, cons, str);
+
+ /* Stack trace. */
+ jobjectArray elements =
+ (*env)->NewObjectArray(env, bt_size, cls_ste, NULL);
+ if (elements != NULL) {
+ t_bt_fun t;
+ t.env = env;
+ t.cls = cls;
+ t.cls_ste = cls_ste;
+ t.cons_ste = cons_ste;
+ t.elements = elements;
+ t.index = 0;
+ t.size = bt_size;
+ coffeecatch_get_backtrace_info(bt_fun, &t);
+ (*env)->CallVoidMethod(env, cause, meth_sste, elements);
+ }
+
+ /* Primary exception */
+ exception = (jthrowable) (*env)->NewObject(env, cls, cons_cause, str, cause);
+ } else {
+ /* Simple exception */
+ exception = (jthrowable) (*env)->NewObject(env, cls, cons, str);
+ }
+
+ /* Throw exception. */
+ if (exception != NULL) {
+ (*env)->Throw(env, exception);
+ } else {
+ (*env)->ThrowNew(env, cls, strdup(message));
+ }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp
new file mode 100644
index 0000000000..0fa7a8a10a
--- /dev/null
+++ b/android/cpp/jni.cpp
@@ -0,0 +1,987 @@
+#include <cstdint>
+#include <cinttypes>
+
+#include <string>
+#include <locale>
+#include <codecvt>
+#include <array>
+#include <vector>
+
+#include <jni.h>
+
+#include <android/native_window_jni.h>
+
+#include <coffeecatch/coffeejni.h>
+
+#include <mbgl/android/jni.hpp>
+#include <mbgl/android/native_map_view.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/platform/android/log_android.hpp>
+#include <mbgl/platform/event.hpp>
+#include <mbgl/platform/log.hpp>
+
+#pragma clang diagnostic ignored "-Wunused-parameter"
+
+#define CPP_TRY_JNI(ENV, CODE) \
+ try { \
+ CODE; \
+ } catch(std::exception e) { \
+ std::string msg("Unhandled C++ exception: "); \
+ msg.append(e.what()); \
+ throw_error(ENV, msg.c_str()); \
+ }
+
+
+namespace mbgl {
+namespace android {
+
+std::string cachePath;
+std::string dataPath;
+std::string apkPath;
+
+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<const char16_t*>(chars), len);
+ env->ReleaseStringChars(jstr, chars);
+ chars = nullptr;
+ str = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t>().to_bytes(ustr);
+ return str;
+}
+
+jstring std_string_to_jstring(JNIEnv* env, std::string str) {
+ std::u16string ustr = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t>().from_bytes(str);
+
+ jstring jstr = env->NewString(reinterpret_cast<const jchar*>(ustr.c_str()), ustr.size());
+ if (jstr == nullptr) {
+ env->ExceptionDescribe();
+ return nullptr;
+ }
+
+ return jstr;
+}
+
+std::vector<std::string> std_vector_string_from_jobject(JNIEnv* env, jobject jlist) {
+ std::vector<std::string> vector;
+
+ if (jlist == nullptr) {
+ if (env->ThrowNew(nullPointerExceptionClass, "List cannot be null.") < 0) {
+ env->ExceptionDescribe();
+ return vector;
+ }
+
+ return vector;
+ }
+
+ jobjectArray array = reinterpret_cast<jobjectArray>(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<jstring>(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<std::string> 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 = nullptr;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, nativeMapView = new NativeMapView(env, obj)));
+ if (nativeMapView == nullptr) {
+ throw_error(env, "Unable to create NativeMapView.");
+ return 0;
+ }
+ jlong mapViewPtr = reinterpret_cast<jlong>(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<NativeMapView*>(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<NativeMapView*>(nativeMapViewPtr);
+ bool ret = false;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = nativeMapView->initializeDisplay()));
+ if (!ret) {
+ 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ bool ret = false;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = nativeMapView->initializeContext()));
+ if (!ret) {
+ 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ bool ret = false;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = nativeMapView->createSurface(ANativeWindow_fromSurface(env, surface))));
+ if (!ret) {
+ 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, nativeMapView->getMap().update()));
+}
+
+void JNICALL nativeCleanup(JNIEnv* env, jobject obj, jlong nativeMapViewPtr) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "nativeCleanup");
+ assert(nativeMapViewPtr != 0);
+ NativeMapView* nativeMapView = reinterpret_cast<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, nativeMapView->getMap().cleanup()));
+}
+
+void JNICALL nativeTerminate(JNIEnv* env, jobject obj, jlong nativeMapViewPtr) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "nativeTerminate");
+ assert(nativeMapViewPtr != 0);
+ NativeMapView* nativeMapView = reinterpret_cast<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ jboolean ret = false;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = nativeMapView->getMap().needsSwap()));
+ return ret;
+}
+
+void JNICALL nativeSwapped(JNIEnv* env, jobject obj, jlong nativeMapViewPtr) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "nativeSwapped");
+ assert(nativeMapViewPtr != 0);
+ NativeMapView* nativeMapView = reinterpret_cast<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, (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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, nativeMapView->getMap().resize(width, height, ratio, fbWidth, fbHeight)));
+}
+
+void JNICALL nativeSetAppliedClasses(JNIEnv* env, jobject obj, jlong nativeMapViewPtr, jobject classes) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetAppliedClasses");
+ assert(nativeMapViewPtr != 0);
+ NativeMapView* nativeMapView = reinterpret_cast<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, nativeMapView->getMap().setAppliedClasses(std_vector_string_from_jobject(env, classes))));
+}
+
+jobject JNICALL nativeGetAppliedClasses(JNIEnv* env, jobject obj, jlong nativeMapViewPtr) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetAppliedClasses");
+ assert(nativeMapViewPtr != 0);
+ NativeMapView* nativeMapView = reinterpret_cast<NativeMapView*>(nativeMapViewPtr);
+ jobject ret = nullptr;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = std_vector_string_to_jobject(env, nativeMapView->getMap().getAppliedClasses())));
+ return ret;
+}
+
+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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ jlong ret = 0;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = nativeMapView->getMap().getDefaultTransitionDuration()));
+ return ret;
+}
+
+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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ jstring ret = nullptr;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = std_string_to_jstring(env, nativeMapView->getMap().getStyleJSON())));
+ return ret;
+}
+
+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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ jstring ret = nullptr;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = std_string_to_jstring(env, nativeMapView->getFileSource().getAccessToken())));
+ return ret;
+}
+
+void JNICALL nativeCancelTransitions(JNIEnv* env, jobject obj, jlong nativeMapViewPtr) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "nativeCancelTransitions");
+ assert(nativeMapViewPtr != 0);
+ NativeMapView* nativeMapView = reinterpret_cast<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(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;
+ }
+
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ double lon = 0.0, lat = 0.0;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ jdouble ret = 0.0;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = nativeMapView->getMap().getScale()));
+ return ret;
+}
+
+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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ jdouble ret = 0.0;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = nativeMapView->getMap().getZoom()));
+ return ret;
+}
+
+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<NativeMapView*>(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;
+ }
+
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ double lon = 0.0, lat = 0.0, zoom = 0.0;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ jdouble ret = 0.0;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = nativeMapView->getMap().getMinZoom()));
+ return ret;
+}
+
+jdouble JNICALL nativeGetMaxZoom(JNIEnv* env, jobject obj, jlong nativeMapViewPtr) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetMaxZoom");
+ assert(nativeMapViewPtr != 0);
+ NativeMapView* nativeMapView = reinterpret_cast<NativeMapView*>(nativeMapViewPtr);
+ jdouble ret = 0.0;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = nativeMapView->getMap().getMaxZoom()));
+ return ret;
+}
+
+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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ jdouble ret = 0.0;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = nativeMapView->getMap().getBearing()));
+ return ret;
+}
+
+void JNICALL nativeResetNorth(JNIEnv* env, jobject obj, jlong nativeMapViewPtr) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "nativeResetNorth");
+ assert(nativeMapViewPtr != 0);
+ NativeMapView* nativeMapView = reinterpret_cast<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, 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<NativeMapView*>(nativeMapViewPtr);
+ jboolean ret = false;
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, ret = nativeMapView->getMap().getDebug()));
+ return ret;
+}
+
+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<NativeMapView*>(nativeMapViewPtr);
+ COFFEE_TRY_JNI(env, CPP_TRY_JNI(env, nativeMapView->getFileSource().setReachability(status)));
+}
+
+}
+
+extern "C" {
+
+extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
+ mbgl::Log::Set<mbgl::AndroidLogBackend>();
+
+ mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnLoad");
+
+ JNIEnv* env = nullptr;
+ jint ret = vm->GetEnv(reinterpret_cast<void**>(&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, "<init>", "(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, "<init>", "(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, "<init>", "()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<JNINativeMethod, 60> methods = {{ // Can remove the extra brace in C++14
+ { "nativeCreate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J", reinterpret_cast<void*>(&nativeCreate) },
+ { "nativeDestroy", "(J)V", reinterpret_cast<void*>(&nativeDestroy) },
+ { "nativeInitializeDisplay", "(J)V", reinterpret_cast<void*>(&nativeInitializeDisplay) },
+ { "nativeTerminateDisplay", "(J)V", reinterpret_cast<void*>(&nativeTerminateDisplay) },
+ { "nativeInitializeContext", "(J)V", reinterpret_cast<void*>(&nativeInitializeContext) },
+ { "nativeTerminateContext", "(J)V", reinterpret_cast<void*>(&nativeTerminateContext) },
+ { "nativeCreateSurface", "(JLandroid/view/Surface;)V", reinterpret_cast<void*>(&nativeCreateSurface) },
+ { "nativeDestroySurface", "(J)V", reinterpret_cast<void*>(&nativeDestroySurface) },
+ { "nativeStart", "(J)V", reinterpret_cast<void*>(&nativeStart) },
+ { "nativeStop", "(J)V", reinterpret_cast<void*>(&nativeStop) },
+ { "nativePause", "(J)V", reinterpret_cast<void*>(&nativePause) },
+ { "nativeResume", "(J)V", reinterpret_cast<void*>(&nativeResume) },
+ { "nativeRun", "(J)V", reinterpret_cast<void*>(&nativeRun) },
+ { "nativeRerender", "(J)V", reinterpret_cast<void*>(&nativeRerender) },
+ { "nativeUpdate", "(J)V", reinterpret_cast<void*>(&nativeUpdate) },
+ { "nativeCleanup", "(J)V", reinterpret_cast<void*>(&nativeCleanup) },
+ { "nativeTerminate", "(J)V", reinterpret_cast<void*>(&nativeTerminate) },
+ { "nativeNeedsSwap", "(J)Z", reinterpret_cast<void*>(&nativeNeedsSwap) },
+ { "nativeSwapped", "(J)V", reinterpret_cast<void*>(&nativeSwapped) },
+ { "nativeResize", "(JIIF)V", reinterpret_cast<void*>(static_cast<void JNICALL(*)(JNIEnv*,jobject,jlong,jint,jint,jfloat)>(&nativeResize)) },
+ { "nativeResize", "(JIIFII)V", reinterpret_cast<void*>(static_cast<void JNICALL(*)(JNIEnv*,jobject,jlong,jint,jint,jfloat,jint,jint)>(&nativeResize)) },
+ { "nativeSetAppliedClasses", "(JLjava/util/List;)V", reinterpret_cast<void*>(&nativeSetAppliedClasses) },
+ { "nativeGetAppliedClasses", "(J)Ljava/util/List;", reinterpret_cast<void*>(&nativeGetAppliedClasses) },
+ { "nativeSetDefaultTransitionDuration", "(JJ)V", reinterpret_cast<void*>(&nativeSetDefaultTransitionDuration) },
+ { "nativeGetDefaultTransitionDuration", "(J)J", reinterpret_cast<void*>(&nativeGetDefaultTransitionDuration) },
+ { "nativeSetStyleUrl", "(JLjava/lang/String;)V", reinterpret_cast<void*>(&nativeSetStyleURL) },
+ { "nativeSetStyleJson", "(JLjava/lang/String;Ljava/lang/String;)V", reinterpret_cast<void*>(&nativeSetStyleJSON) },
+ { "nativeGetStyleJson", "(J)Ljava/lang/String;", reinterpret_cast<void*>(&nativeGetStyleJSON) },
+ { "nativeSetAccessToken", "(JLjava/lang/String;)V", reinterpret_cast<void*>(&nativeSetAccessToken) },
+ { "nativeGetAccessToken", "(J)Ljava/lang/String;", reinterpret_cast<void*>(&nativeGetAccessToken) },
+ { "nativeCancelTransitions", "(J)V", reinterpret_cast<void*>(&nativeCancelTransitions) },
+ { "nativeMoveBy", "(JDDD)V", reinterpret_cast<void*>(&nativeMoveBy) },
+ { "nativeSetLonLat", "(JLcom/mapbox/mapboxgl/lib/LonLat;D)V", reinterpret_cast<void*>(&nativeSetLonLat) },
+ { "nativeGetLonLat", "(J)Lcom/mapbox/mapboxgl/lib/LonLat;", reinterpret_cast<void*>(&nativeGetLonLat) },
+ { "nativeStartPanning", "(J)V", reinterpret_cast<void*>(&nativeStartPanning) },
+ { "nativeStopPanning", "(J)V", reinterpret_cast<void*>(&nativeStopPanning) },
+ { "nativeResetPosition", "(J)V", reinterpret_cast<void*>(&nativeResetPosition) },
+ { "nativeScaleBy", "(JDDDD)V", reinterpret_cast<void*>(&nativeScaleBy) },
+ { "nativeSetScale", "(JDDDD)V", reinterpret_cast<void*>(&nativeSetScale) },
+ { "nativeGetScale", "(J)D", reinterpret_cast<void*>(&nativeGetScale) },
+ { "nativeSetZoom", "(JDD)V", reinterpret_cast<void*>(&nativeSetZoom) },
+ { "nativeGetZoom", "(J)D", reinterpret_cast<void*>(&nativeGetZoom) },
+ { "nativeSetLonLatZoom", "(JLcom/mapbox/mapboxgl/lib/LonLatZoom;D)V", reinterpret_cast<void*>(&nativeSetLonLatZoom) },
+ { "nativeGetLonLatZoom", "(J)Lcom/mapbox/mapboxgl/lib/LonLatZoom;", reinterpret_cast<void*>(&nativeGetLonLatZoom) },
+ { "nativeResetZoom", "(J)V", reinterpret_cast<void*>(&nativeResetZoom) },
+ { "nativeStartPanning", "(J)V", reinterpret_cast<void*>(&nativeStartScaling) },
+ { "nativeStopPanning", "(J)V", reinterpret_cast<void*>(&nativeStopScaling) },
+ { "nativeGetMinZoom", "(J)D", reinterpret_cast<void*>(&nativeGetMinZoom) },
+ { "nativeGetMaxZoom", "(J)D", reinterpret_cast<void*>(&nativeGetMaxZoom) },
+ { "nativeRotateBy", "(JDDDDD)V", reinterpret_cast<void*>(&nativeRotateBy) },
+ { "nativeSetBearing", "(JDD)V", reinterpret_cast<void*>(static_cast<void JNICALL(*)(JNIEnv*,jobject,jlong,jdouble,jdouble)>(&nativeSetBearing)) },
+ { "nativeSetBearing", "(JDDD)V", reinterpret_cast<void*>(static_cast<void JNICALL(*)(JNIEnv*,jobject,jlong,jdouble,jdouble,jdouble)>(&nativeSetBearing)) },
+ { "nativeGetBearing", "(J)D", reinterpret_cast<void*>(&nativeGetBearing) },
+ { "nativeResetNorth", "(J)V", reinterpret_cast<void*>(&nativeResetNorth) },
+ { "nativeStartRotating", "(J)V", reinterpret_cast<void*>(&nativeStartRotating) },
+ { "nativeStopRotating", "(J)V", reinterpret_cast<void*>(&nativeStopRotating) },
+ { "nativeSetDebug", "(JZ)V", reinterpret_cast<void*>(&nativeSetDebug) },
+ { "nativeToggleDebug", "(J)V", reinterpret_cast<void*>(&nativeToggleDebug) },
+ { "nativeGetDebug", "(J)Z", reinterpret_cast<void*>(&nativeGetDebug) },
+ { "nativeSetReachability", "(JZ)V", reinterpret_cast<void*>(&nativeSetReachability) }
+ }};
+
+ if (env->RegisterNatives(nativeMapViewClass, methods.data(), methods.size()) < 0) {
+ env->ExceptionDescribe();
+ return JNI_ERR;
+ }
+
+ lonLatClass = reinterpret_cast<jclass>(env->NewGlobalRef(lonLatClass));
+ if (lonLatClass == nullptr) {
+ env->ExceptionDescribe();
+ return JNI_ERR;
+ }
+
+ lonLatZoomClass = reinterpret_cast<jclass>(env->NewGlobalRef(lonLatZoomClass));
+ if (lonLatZoomClass == nullptr) {
+ env->ExceptionDescribe();
+ env->DeleteGlobalRef(lonLatClass);
+ return JNI_ERR;
+ }
+
+ runtimeExceptionClass = reinterpret_cast<jclass>(env->NewGlobalRef(runtimeExceptionClass));
+ if (runtimeExceptionClass == nullptr) {
+ env->ExceptionDescribe();
+ env->DeleteGlobalRef(lonLatClass);
+ env->DeleteGlobalRef(lonLatZoomClass);
+ return JNI_ERR;
+ }
+
+ nullPointerExceptionClass = reinterpret_cast<jclass>(env->NewGlobalRef(nullPointerExceptionClass));
+ if (nullPointerExceptionClass == nullptr) {
+ env->ExceptionDescribe();
+ env->DeleteGlobalRef(lonLatClass);
+ env->DeleteGlobalRef(lonLatZoomClass);
+ env->DeleteGlobalRef(runtimeExceptionClass);
+ return JNI_ERR;
+ }
+
+ arrayListClass = reinterpret_cast<jclass>(env->NewGlobalRef(arrayListClass));
+ if (arrayListClass == nullptr) {
+ env->ExceptionDescribe();
+ env->DeleteGlobalRef(lonLatClass);
+ env->DeleteGlobalRef(lonLatZoomClass);
+ env->DeleteGlobalRef(runtimeExceptionClass);
+ env->DeleteGlobalRef(nullPointerExceptionClass);
+ return JNI_ERR;
+ }
+
+ 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<void**>(&env), JNI_VERSION_1_6);
+ if (ret != JNI_OK) {
+ mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
+ return;
+ }
+
+ 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 0000000000..003df912ce
--- /dev/null
+++ b/android/cpp/native_map_view.cpp
@@ -0,0 +1,738 @@
+#include <cstdlib>
+#include <ctime>
+#include <memory>
+#include <list>
+#include <tuple>
+
+#include <sys/system_properties.h>
+
+#include <GLES2/gl2.h>
+
+#include <mbgl/android/native_map_view.hpp>
+#include <mbgl/android/jni.hpp>
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/platform/event.hpp>
+#include <mbgl/platform/log.hpp>
+#include <mbgl/platform/gl.hpp>
+#include <mbgl/util/std.hpp>
+
+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());
+ } 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());
+ } else {
+ char buf[513];
+ for (int len = std::strlen(reinterpret_cast<const char *>(str)), pos = 0; len > 0; len -= 512, pos += 512) {
+ strncpy(buf, reinterpret_cast<const char *>(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<void**>(&env), JNI_VERSION_1_6);
+ if (ret == JNI_OK) {
+ env->DeleteGlobalRef(obj);
+ } else {
+ mbgl::Log::Debug(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
+ }
+ obj = nullptr;
+ vm = nullptr;
+}
+
+void NativeMapView::make_active() {
+ mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::make_active");
+ 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());
+ }
+ } else {
+ mbgl::Log::Info(mbgl::Event::Android, "Not activating as we are not ready");
+ }
+}
+
+void NativeMapView::make_inactive() {
+ mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::make_inactive");
+ 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());
+ }
+}
+
+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());
+ }
+ 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::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());
+ terminateDisplay();
+ return false;
+ }
+
+ EGLint major, minor;
+ if (!eglInitialize(display, &major, &minor)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglInitialize() returned error %d", eglGetError());
+ terminateDisplay();
+ return false;
+ }
+ if ((major <= 1) && (minor < 3)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "EGL version is too low, need 1.3, got %d.%d", major, minor);
+ terminateDisplay();
+ return false;
+ }
+
+ 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
+ char prop[PROP_VALUE_MAX];
+ __system_property_get("ro.kernel.qemu", prop);
+ bool inEmulator = strtol(prop, nullptr, 0) == 1;
+ 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());
+ terminateDisplay();
+ return false;
+ }
+ if (numConfigs < 1) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig() returned no configs.");
+ terminateDisplay();
+ return false;
+ }
+
+ const std::unique_ptr<EGLConfig[]> configs = mbgl::util::make_unique<EGLConfig[]>(numConfigs);
+ if (!eglChooseConfig(display, configAttribs, configs.get(), numConfigs, &numConfigs)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig() returned error %d", eglGetError());
+ terminateDisplay();
+ return false;
+ }
+
+ config = chooseConfig(configs.get(), numConfigs);
+ if (config == nullptr) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "No config chosen");
+ terminateDisplay();
+ return false;
+ }
+
+ if (!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib() returned error %d", eglGetError());
+ terminateDisplay();
+ return false;
+ }
+ mbgl::Log::Info(mbgl::Event::OpenGL, "Chosen window format is %d", format);
+
+ return true;
+}
+
+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());
+ }
+ 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());
+ }
+
+ if (!eglTerminate(display)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglTerminate() returned error %d", eglGetError());
+ }
+ }
+
+ display = EGL_NO_DISPLAY;
+ config = nullptr;
+ format = -1;
+}
+
+bool 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());
+ terminateContext();
+ return false;
+ }
+
+ return true;
+}
+
+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());
+ }
+
+ if (context != EGL_NO_CONTEXT) {
+ if (!eglDestroyContext(display, context)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroyContext() returned error %d", eglGetError());
+ }
+ }
+ }
+
+ context = EGL_NO_CONTEXT;
+}
+
+bool 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());
+ destroySurface();
+ return false;
+ }
+
+ if (!firstTime) {
+ firstTime = true;
+
+ if (!eglMakeCurrent(display, surface, surface, context)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent() returned error %d", eglGetError());
+ }
+
+ log_gl_string(GL_VENDOR, "Vendor");
+ log_gl_string(GL_RENDERER, "Renderer");
+ log_gl_string(GL_VERSION, "Version");
+ 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());
+ }
+ }
+
+ resume();
+
+ return true;
+}
+
+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());
+ }
+ }
+
+ 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 <buffer_format, depth_stencil_format, is_not_conformant, is_caveat, config_num, config_id>
+typedef std::tuple<BufferFormat, DepthStencilFormat, bool, bool, int, EGLConfig> 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<ConfigProperties> 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());
+ return nullptr;
+ }
+
+ if (!eglGetConfigAttrib(display, configs[i], EGL_CONFORMANT, &conformant)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib(EGL_CONFORMANT) returned error %d", eglGetError());
+ return nullptr;
+ }
+
+ if (!eglGetConfigAttrib(display, configs[i], EGL_BUFFER_SIZE, &bits)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib(EGL_BUFFER_SIZE) returned error %d", eglGetError());
+ return nullptr;
+ }
+
+ if (!eglGetConfigAttrib(display, configs[i], EGL_RED_SIZE, &red)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib(EGL_RED_SIZE) returned error %d", eglGetError());
+ return nullptr;
+ }
+
+ if (!eglGetConfigAttrib(display, configs[i], EGL_GREEN_SIZE, &green)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib(EGL_GREEN_SIZE) returned error %d", eglGetError());
+ return nullptr;
+ }
+
+ if (!eglGetConfigAttrib(display, configs[i], EGL_BLUE_SIZE, &blue)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib(EGL_BLUE_SIZE) returned error %d", eglGetError());
+ return nullptr;
+ }
+
+ if (!eglGetConfigAttrib(display, configs[i], EGL_ALPHA_SIZE, &alpha)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib(EGL_ALPHA_SIZE) returned error %d", eglGetError());
+ return nullptr;
+ }
+
+ 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());
+ return nullptr;
+ }
+
+ if (!eglGetConfigAttrib(display, configs[i], EGL_DEPTH_SIZE, &depth)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib(EGL_DEPTH_SIZE) returned error %d", eglGetError());
+ return nullptr;
+ }
+
+ if (!eglGetConfigAttrib(display, configs[i], EGL_STENCIL_SIZE, &stencil)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib(EGL_STENCIL_SIZE) returned error %d", eglGetError());
+ return nullptr;
+ }
+
+ if (!eglGetConfigAttrib(display, configs[i], EGL_SAMPLE_BUFFERS, &sampleBuffers)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib(EGL_SAMPLE_BUFFERS) returned error %d", eglGetError());
+ return nullptr;
+ }
+
+ if (!eglGetConfigAttrib(display, configs[i], EGL_SAMPLES, &samples)) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib(EGL_SAMPLES) returned error %d", eglGetError());
+ return nullptr;
+ }
+
+ 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<const char*>(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 = (gl::PFNGLBINDVERTEXARRAYPROC)eglGetProcAddress("glBindVertexArrayOES");
+ gl::DeleteVertexArrays = (gl::PFNGLDELETEVERTEXARRAYSPROC)eglGetProcAddress("glDeleteVertexArraysOES");
+ gl::GenVertexArrays = (gl::PFNGLGENVERTEXARRAYSPROC)eglGetProcAddress("glGenVertexArraysOES");
+ gl::IsVertexArray = (gl::PFNGLISVERTEXARRAYPROC)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.");
+ }
+ }
+}
+
+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::notify_map_change(mbgl::MapChange, mbgl::timestamp) {
+ mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::notify_map_change()");
+
+ assert(vm != nullptr);
+ assert(obj != nullptr);
+
+ JavaVMAttachArgs args = {
+ JNI_VERSION_1_2,
+ "NativeMapView::notify_map_change()",
+ NULL
+ };
+
+ jint ret;
+ JNIEnv* env = nullptr;
+ bool detach = false;
+ ret = vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
+ if (ret != JNI_OK) {
+ if (ret != JNI_EDETACHED) {
+ mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
+ return;
+ } else {
+ ret = vm->AttachCurrentThread(&env, &args);
+ if (ret != JNI_OK) {
+ mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret);
+ return;
+ }
+ 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);
+ return;
+ }
+ }
+ 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<void**>(&env), JNI_VERSION_1_6);
+ if (ret != JNI_OK) {
+ if (ret != JNI_EDETACHED) {
+ mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
+ return;
+ } else {
+ ret = vm->AttachCurrentThread(&env, &args);
+ if (ret != JNI_OK) {
+ mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret);
+ return;
+ }
+ 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);
+ return;
+ }
+ }
+ env = nullptr;
+}
+
+}
+}
diff --git a/android/java/.gitignore b/android/java/.gitignore
new file mode 100644
index 0000000000..2e8c5e2ba4
--- /dev/null
+++ b/android/java/.gitignore
@@ -0,0 +1,18 @@
+# Gradle files
+.gradle/
+
+# IntelliJ files
+.idea
+*.iml
+
+# Build files
+build/
+*/build/
+*.so
+*.apk
+
+# Lib assets
+lib/src/main/assets/
+
+# Local settings
+local.properties
diff --git a/android/java/app/build.gradle b/android/java/app/build.gradle
new file mode 100644
index 0000000000..17084ad152
--- /dev/null
+++ b/android/java/app/build.gradle
@@ -0,0 +1,40 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.1"
+
+ defaultConfig {
+ applicationId "com.mapbox.mapboxgl.app"
+ minSdkVersion 14
+ targetSdkVersion 21
+ versionCode 2
+ versionName "0.1.1"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
+ packagingOptions {
+ exclude 'META-INF/LICENSE.txt'
+ exclude 'META-INF/NOTICE.txt'
+ }
+
+
+
+ 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.0'
+ compile 'com.android.support:appcompat-v7:21.0.0'
+}
diff --git a/android/java/app/lint.xml b/android/java/app/lint.xml
new file mode 100644
index 0000000000..94058fd4df
--- /dev/null
+++ b/android/java/app/lint.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lint>
+ <!-- Ignore errors caused by icon burning -->
+ <issue id="IconLauncherShape" severity="ignore">
+ <ignore path="res/drawable-mdpi/icon.png" />
+ <ignore path="res/drawable-hdpi/icon.png" />
+ <ignore path="res/drawable-xdpi/icon.png" />
+ <ignore path="res/drawable-xxdpi/icon.png" />
+ <ignore path="res/drawable-mdpi/icon_burned.png" />
+ <ignore path="res/drawable-hdpi/icon_burned.png" />
+ <ignore path="res/drawable-xdpi/icon_burned.png" />
+ <ignore path="res/drawable-xxdpi/icon_burned.png" />
+ </issue>
+ <issue id="IconDuplicates" severity="ignore">
+ <ignore path="res/drawable-mdpi/icon.png" />
+ <ignore path="res/drawable-hdpi/icon.png" />
+ <ignore path="res/drawable-xdpi/icon.png" />
+ <ignore path="res/drawable-xxdpi/icon.png" />
+ <ignore path="res/drawable-mdpi/icon_burned.png" />
+ <ignore path="res/drawable-hdpi/icon_burned.png" />
+ <ignore path="res/drawable-xdpi/icon_burned.png" />
+ <ignore path="res/drawable-xxdpi/icon_burned.png" />
+ </issue>
+ <issue id="UnusedResources" severity="ignore">
+ <ignore path="res/drawable-mdpi/icon.png" />
+ <ignore path="res/drawable-hdpi/icon.png" />
+ <ignore path="res/drawable-xdpi/icon.png" />
+ <ignore path="res/drawable-xxdpi/icon.png" />
+ <ignore path="res/drawable-mdpi/icon_burned.png" />
+ <ignore path="res/drawable-hdpi/icon_burned.png" />
+ <ignore path="res/drawable-xdpi/icon_burned.png" />
+ <ignore path="res/drawable-xxdpi/icon_burned.png" />
+ </issue>
+</lint>
diff --git a/android/java/app/src/main/AndroidManifest.xml b/android/java/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..bd9d2da06d
--- /dev/null
+++ b/android/java/app/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.mapbox.mapboxgl.app">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <application
+ android:allowBackup="false"
+ android:icon="@drawable/icon_burned"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme">
+ <activity
+ android:name="com.mapbox.mapboxgl.app.MainActivity"
+ android:label="@string/app_name"
+ android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
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 0000000000..2005359da9
--- /dev/null
+++ b/android/java/app/src/main/java/com/mapbox/mapboxgl/app/MainActivity.java
@@ -0,0 +1,255 @@
+package com.mapbox.mapboxgl.app;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+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 com.mapbox.mapboxgl.lib.LonLatZoom;
+import com.mapbox.mapboxgl.lib.MapView;
+
+public class MainActivity extends ActionBarActivity {
+
+ //
+ // Static members
+ //
+
+ // Tag used for logging
+ private static final String TAG = "MainActivity";
+
+ // Used for GPS
+ private static final String SINGLE_LOCATION_UPDATE_ACTION = "com.mapbox.mapboxgl.app.SINGLE_LOCATION_UPDATE_ACTION";
+
+ //
+ // Instance members
+ //
+
+ // Holds the MapFragment
+ private MapFragment mMapFragment;
+
+ // The FPS label
+ private TextView mFpsTextView;
+
+ // The compass
+ private ImageView mCompassView;
+
+ // Used for GPS
+ private LocationManager mLocationManager;
+ private PendingIntent mLocationPendingIntent;
+ private LocationReceiver mLocationReceiver;
+
+ //
+ // Lifecycle events
+ //
+
+ // Called when activity is created
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.v(TAG, "onCreate");
+
+ Intent locationIntent = new Intent(SINGLE_LOCATION_UPDATE_ACTION);
+ mLocationManager = (LocationManager)getSystemService(getApplicationContext().LOCATION_SERVICE);
+ mLocationPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, locationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ // 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());
+ }
+
+ // Called when our app goes into the background
+ @Override
+ public void onPause() {
+ super.onPause();
+ Log.v(TAG, "onPause");
+
+ // Cancel any outstanding GPS
+ if (mLocationReceiver != null) {
+ getApplicationContext().unregisterReceiver(mLocationReceiver);
+ mLocationReceiver = null;
+ }
+
+ mLocationManager.removeUpdates(mLocationPendingIntent);
+ }
+
+ //
+ // 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:
+ // Get a GPS position
+ Criteria criteria = new Criteria();
+ criteria.setAccuracy(Criteria.ACCURACY_LOW);
+ String provider = mLocationManager.getBestProvider(criteria, true);
+ Location location = mLocationManager.getLastKnownLocation(provider);
+ if (location != null) {
+ LonLatZoom coordinate = new LonLatZoom(location.getLongitude(), location.getLatitude(), 15);
+ mMapFragment.getMap().setCenterCoordinate(coordinate, true);
+ }
+ IntentFilter locationIntentFilter = new IntentFilter(SINGLE_LOCATION_UPDATE_ACTION);
+ mLocationReceiver = new LocationReceiver();
+ getApplicationContext().registerReceiver(mLocationReceiver, locationIntentFilter);
+ mLocationManager.requestSingleUpdate(provider, mLocationPendingIntent);
+ 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);
+ }
+ }
+
+ // This class handles location events
+ private class LocationReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ context.unregisterReceiver(this);
+
+ Location location = (Location)intent.getExtras().get(LocationManager.KEY_LOCATION_CHANGED);
+ if (location != null) {
+ LonLatZoom coordinate = new LonLatZoom(location.getLongitude(), location.getLatitude(), 15);
+ mMapFragment.getMap().setCenterCoordinate(coordinate, true);
+ }
+
+ mLocationManager.removeUpdates(mLocationPendingIntent);
+ mLocationReceiver = null;
+ }
+ }
+
+ // 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 0000000000..25723ea975
--- /dev/null
+++ b/android/java/app/src/main/java/com/mapbox/mapboxgl/app/MapFragment.java
@@ -0,0 +1,119 @@
+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;
+
+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
+ mMap.setAccessToken("access token goes here");
+
+ // 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 0000000000..077dcec800
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-hdpi/ic_action_about.png
Binary files 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 0000000000..e1bf5807b0
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-hdpi/ic_action_location_found.png
Binary files 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 0000000000..f9875161da
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-hdpi/icon.png
Binary files 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 0000000000..f9875161da
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-hdpi/icon_burned.png
Binary files 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 0000000000..58e7e08d24
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-mdpi/compass.png
Binary files 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 0000000000..624e745b69
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-mdpi/ic_action_about.png
Binary files 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 0000000000..33f660f2d8
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-mdpi/ic_action_location_found.png
Binary files 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 0000000000..3c0cc701a0
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-mdpi/icon.png
Binary files 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 0000000000..3c0cc701a0
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-mdpi/icon_burned.png
Binary files 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 0000000000..3be3152704
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-xhdpi/ic_action_about.png
Binary files 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 0000000000..4c06441f9a
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-xhdpi/ic_action_location_found.png
Binary files 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 0000000000..0cbf599362
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-xhdpi/icon.png
Binary files 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 0000000000..0cbf599362
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-xhdpi/icon_burned.png
Binary files 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 0000000000..0fe809b90d
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-xxhdpi/ic_action_about.png
Binary files 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 0000000000..43f18ea01d
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-xxhdpi/ic_action_location_found.png
Binary files 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 0000000000..6ac3191ebf
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-xxhdpi/icon.png
Binary files 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 0000000000..6ac3191ebf
--- /dev/null
+++ b/android/java/app/src/main/res/drawable-xxhdpi/icon_burned.png
Binary files 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 0000000000..d14f2ead99
--- /dev/null
+++ b/android/java/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar_main"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:minHeight="?attr/actionBarSize"
+ android:background="?attr/colorPrimary">
+
+ <Spinner
+ android:id="@+id/spinner_style"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </android.support.v7.widget.Toolbar>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <fragment
+ android:name="com.mapbox.mapboxgl.app.MapFragment"
+ android:id="@+id/fragment_map"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="${packageName}.${activityClass}"
+ tools:layout="@layout/fragment_main" />
+
+ <ImageView
+ android:id="@+id/view_compass"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right|top"
+ android:padding="10dp"
+ android:contentDescription="Map compass. Click to reset the map rotation to North."
+ android:src="@drawable/compass"/>
+
+ <TextView
+ android:id="@+id/view_fps"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:text="@string/label_fps"
+ android:padding="10dp" />
+
+ </FrameLayout>
+</LinearLayout>
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 0000000000..093e82e1e8
--- /dev/null
+++ b/android/java/app/src/main/res/layout/fragment_main.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.mapbox.mapboxgl.lib.MapView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/map"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
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 0000000000..0b9c10aff0
--- /dev/null
+++ b/android/java/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:id="@+id/action_gps"
+ android:icon="@drawable/ic_action_location_found"
+ android:title="@string/action_gps"
+ app:showAsAction="ifRoom" />
+ <item android:id="@+id/action_debug"
+ android:icon="@drawable/ic_action_about"
+ android:title="@string/action_debug"
+ app:showAsAction="ifRoom" />
+</menu>
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 0000000000..05f821ee25
--- /dev/null
+++ b/android/java/app/src/main/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">Mapbox GL</string>
+
+ <string name="action_gps">Go to GPS location</string>
+
+ <string name="action_debug">Toggle debug mode</string>
+
+ <string name="label_fps">FPS:</string>
+
+ <string-array name="style_list">
+ <item>Bright</item>
+ <item>Basic</item>
+ <item>Outdoors</item>
+ <item>Satellite</item>
+ <!--<item>Pencil</item>-->
+ <!--<item>Empty</item>-->
+ </string-array>
+
+</resources>
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 0000000000..2399c78bee
--- /dev/null
+++ b/android/java/app/src/main/res/values/styles.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="AppBaseTheme" parent="@style/Theme.AppCompat.Light.NoActionBar" />
+ <style name="AppTheme" parent="AppBaseTheme" />
+</resources>
diff --git a/android/java/build.gradle b/android/java/build.gradle
new file mode 100644
index 0000000000..d728479126
--- /dev/null
+++ b/android/java/build.gradle
@@ -0,0 +1,15 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.0.0-rc4'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/android/java/gradle/wrapper/gradle-wrapper.jar b/android/java/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..8c0fb64a86
--- /dev/null
+++ b/android/java/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/android/java/gradle/wrapper/gradle-wrapper.properties b/android/java/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..5adf988603
--- /dev/null
+++ b/android/java/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Dec 06 12:43:10 EST 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/android/java/gradlew b/android/java/gradlew
new file mode 100755
index 0000000000..91a7e269e1
--- /dev/null
+++ b/android/java/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/android/java/gradlew.bat b/android/java/gradlew.bat
new file mode 100644
index 0000000000..8a0b282aa6
--- /dev/null
+++ b/android/java/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/android/java/lib/build.gradle b/android/java/lib/build.gradle
new file mode 100644
index 0000000000..14749095bb
--- /dev/null
+++ b/android/java/lib/build.gradle
@@ -0,0 +1,39 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.1"
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 21
+ versionCode 2
+ versionName "0.1.1"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
+ buildTypes {
+ debug {
+ jniDebuggable true
+ }
+
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+}
+
+configurations {
+ all*.exclude group: 'commons-logging', module: 'commons-logging'
+}
+
+dependencies {
+ compile 'commons-validator:commons-validator:1.4.0'
+ compile 'com.android.support:support-annotations:21.0.0'
+ compile 'com.android.support:support-v4:21.0.0'
+}
diff --git a/android/java/lib/lint.xml b/android/java/lib/lint.xml
new file mode 100644
index 0000000000..309f4e478b
--- /dev/null
+++ b/android/java/lib/lint.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<lint>
+</lint>
diff --git a/android/java/lib/src/main/AndroidManifest.xml b/android/java/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..7c7dac9a01
--- /dev/null
+++ b/android/java/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,9 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.mapbox.mapboxgl.lib">
+
+ <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
+</manifest>
diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/BaseGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/BaseGestureDetector.java
new file mode 100644
index 0000000000..dafac2a1ab
--- /dev/null
+++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/BaseGestureDetector.java
@@ -0,0 +1,150 @@
+package com.almeros.android.multitouch;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * @author Almer Thie (code.almeros.com)
+ * Copyright (c) 2013, Almer Thie (code.almeros.com)
+ *
+ * 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.
+ */
+public abstract class BaseGestureDetector {
+ protected final Context mContext;
+ protected boolean mGestureInProgress;
+
+ protected MotionEvent mPrevEvent;
+ protected MotionEvent mCurrEvent;
+
+ protected float mCurrPressure;
+ protected float mPrevPressure;
+ protected long mTimeDelta;
+
+
+ /**
+ * This value is the threshold ratio between the previous combined pressure
+ * and the current combined pressure. When pressure decreases rapidly
+ * between events the position values can often be imprecise, as it usually
+ * indicates that the user is in the process of lifting a pointer off of the
+ * device. This value was tuned experimentally.
+ */
+ protected static final float PRESSURE_THRESHOLD = 0.67f;
+
+
+ public BaseGestureDetector(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * All gesture detectors need to be called through this method to be able to
+ * detect gestures. This method delegates work to handler methods
+ * (handleStartProgressEvent, handleInProgressEvent) implemented in
+ * extending classes.
+ *
+ * @param event
+ * @return
+ */
+ public boolean onTouchEvent(MotionEvent event){
+ final int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
+ if (!mGestureInProgress) {
+ handleStartProgressEvent(actionCode, event);
+ } else {
+ handleInProgressEvent(actionCode, event);
+ }
+ return true;
+ }
+
+ /**
+ * Called when the current event occurred when NO gesture is in progress
+ * yet. The handling in this implementation may set the gesture in progress
+ * (via mGestureInProgress) or out of progress
+ * @param actionCode
+ * @param event
+ */
+ protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event);
+
+ /**
+ * Called when the current event occurred when a gesture IS in progress. The
+ * handling in this implementation may set the gesture out of progress (via
+ * mGestureInProgress).
+ * @param actionCode
+ * @param event
+ */
+ protected abstract void handleInProgressEvent(int actionCode, MotionEvent event);
+
+
+ protected void updateStateByEvent(MotionEvent curr){
+ final MotionEvent prev = mPrevEvent;
+
+ // Reset mCurrEvent
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ mCurrEvent = MotionEvent.obtain(curr);
+
+
+ // Delta time
+ mTimeDelta = curr.getEventTime() - prev.getEventTime();
+
+ // Pressure
+ mCurrPressure = curr.getPressure(curr.getActionIndex());
+ mPrevPressure = prev.getPressure(prev.getActionIndex());
+ }
+
+ protected void resetState() {
+ if (mPrevEvent != null) {
+ mPrevEvent.recycle();
+ mPrevEvent = null;
+ }
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ mGestureInProgress = false;
+ }
+
+
+ /**
+ * Returns {@code true} if a gesture is currently in progress.
+ * @return {@code true} if a gesture is currently in progress, {@code false} otherwise.
+ */
+ public boolean isInProgress() {
+ return mGestureInProgress;
+ }
+
+ /**
+ * Return the time difference in milliseconds between the previous accepted
+ * GestureDetector event and the current GestureDetector event.
+ *
+ * @return Time difference since the last move event in milliseconds.
+ */
+ public long getTimeDelta() {
+ return mTimeDelta;
+ }
+
+ /**
+ * Return the event time of the current GestureDetector event being
+ * processed.
+ *
+ * @return Current GestureDetector event time in milliseconds.
+ */
+ public long getEventTime() {
+ return mCurrEvent.getEventTime();
+ }
+
+}
diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/MoveGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/MoveGestureDetector.java
new file mode 100644
index 0000000000..42d6bf32c4
--- /dev/null
+++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/MoveGestureDetector.java
@@ -0,0 +1,174 @@
+package com.almeros.android.multitouch;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import com.almeros.android.multitouch.BaseGestureDetector;
+
+/**
+ * @author Almer Thie (code.almeros.com)
+ * Copyright (c) 2013, Almer Thie (code.almeros.com)
+ *
+ * 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.
+ */
+public class MoveGestureDetector extends BaseGestureDetector {
+
+ /**
+ * Listener which must be implemented which is used by MoveGestureDetector
+ * to perform callbacks to any implementing class which is registered to a
+ * MoveGestureDetector via the constructor.
+ *
+ * @see MoveGestureDetector.SimpleOnMoveGestureListener
+ */
+ public interface OnMoveGestureListener {
+ public boolean onMove(MoveGestureDetector detector);
+ public boolean onMoveBegin(MoveGestureDetector detector);
+ public void onMoveEnd(MoveGestureDetector detector);
+ }
+
+ /**
+ * Helper class which may be extended and where the methods may be
+ * implemented. This way it is not necessary to implement all methods
+ * of OnMoveGestureListener.
+ */
+ public static class SimpleOnMoveGestureListener implements OnMoveGestureListener {
+ public boolean onMove(MoveGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onMoveBegin(MoveGestureDetector detector) {
+ return true;
+ }
+
+ public void onMoveEnd(MoveGestureDetector detector) {
+ // Do nothing, overridden implementation may be used
+ }
+ }
+
+ private static final PointF FOCUS_DELTA_ZERO = new PointF();
+
+ private final OnMoveGestureListener mListener;
+
+ private PointF mCurrFocusInternal;
+ private PointF mPrevFocusInternal;
+ private PointF mFocusExternal = new PointF();
+ private PointF mFocusDeltaExternal = new PointF();
+
+
+ public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
+ super(context);
+ mListener = listener;
+ }
+
+ @Override
+ protected void handleStartProgressEvent(int actionCode, MotionEvent event){
+ switch (actionCode) {
+ case MotionEvent.ACTION_DOWN:
+ resetState(); // In case we missed an UP/CANCEL event
+
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ updateStateByEvent(event);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ mGestureInProgress = mListener.onMoveBegin(this);
+ break;
+ }
+ }
+
+ @Override
+ protected void handleInProgressEvent(int actionCode, MotionEvent event){
+ switch (actionCode) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mListener.onMoveEnd(this);
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ updateStateByEvent(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit. This can help filter shaky data as a
+ // finger is lifted.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
+ final boolean updatePrevious = mListener.onMove(this);
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+
+ protected void updateStateByEvent(MotionEvent curr) {
+ super.updateStateByEvent(curr);
+
+ final MotionEvent prev = mPrevEvent;
+
+ // Focus intenal
+ mCurrFocusInternal = determineFocalPoint(curr);
+ mPrevFocusInternal = determineFocalPoint(prev);
+
+ // Focus external
+ // - Prevent skipping of focus delta when a finger is added or removed
+ boolean mSkipNextMoveEvent = prev.getPointerCount() != curr.getPointerCount();
+ mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x, mCurrFocusInternal.y - mPrevFocusInternal.y);
+
+ // - Don't directly use mFocusInternal (or skipping will occur). Add
+ // unskipped delta values to mFocusExternal instead.
+ mFocusExternal.x += mFocusDeltaExternal.x;
+ mFocusExternal.y += mFocusDeltaExternal.y;
+ }
+
+ /**
+ * Determine (multi)finger focal point (a.k.a. center point between all
+ * fingers)
+ *
+ * @param MotionEvent e
+ * @return PointF focal point
+ */
+ private PointF determineFocalPoint(MotionEvent e){
+ // Number of fingers on screen
+ final int pCount = e.getPointerCount();
+ float x = 0f;
+ float y = 0f;
+
+ for(int i = 0; i < pCount; i++){
+ x += e.getX(i);
+ y += e.getY(i);
+ }
+
+ return new PointF(x/pCount, y/pCount);
+ }
+
+ public float getFocusX() {
+ return mFocusExternal.x;
+ }
+
+ public float getFocusY() {
+ return mFocusExternal.y;
+ }
+
+ public PointF getFocusDelta() {
+ return mFocusDeltaExternal;
+ }
+
+}
diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/RotateGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/RotateGestureDetector.java
new file mode 100644
index 0000000000..ba27af18ac
--- /dev/null
+++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/RotateGestureDetector.java
@@ -0,0 +1,169 @@
+package com.almeros.android.multitouch;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * @author Almer Thie (code.almeros.com)
+ * Copyright (c) 2013, Almer Thie (code.almeros.com)
+ *
+ * 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.
+ */
+public class RotateGestureDetector extends TwoFingerGestureDetector {
+
+ /**
+ * Listener which must be implemented which is used by RotateGestureDetector
+ * to perform callbacks to any implementing class which is registered to a
+ * RotateGestureDetector via the constructor.
+ *
+ * @see RotateGestureDetector.SimpleOnRotateGestureListener
+ */
+ public interface OnRotateGestureListener {
+ public boolean onRotate(RotateGestureDetector detector);
+ public boolean onRotateBegin(RotateGestureDetector detector);
+ public void onRotateEnd(RotateGestureDetector detector);
+ }
+
+ /**
+ * Helper class which may be extended and where the methods may be
+ * implemented. This way it is not necessary to implement all methods
+ * of OnRotateGestureListener.
+ */
+ public static class SimpleOnRotateGestureListener implements OnRotateGestureListener {
+ public boolean onRotate(RotateGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onRotateBegin(RotateGestureDetector detector) {
+ return true;
+ }
+
+ public void onRotateEnd(RotateGestureDetector detector) {
+ // Do nothing, overridden implementation may be used
+ }
+ }
+
+
+ private final OnRotateGestureListener mListener;
+ private boolean mSloppyGesture;
+
+ public RotateGestureDetector(Context context, OnRotateGestureListener listener) {
+ super(context);
+ mListener = listener;
+ }
+
+ @Override
+ protected void handleStartProgressEvent(int actionCode, MotionEvent event){
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // At least the second finger is on screen now
+
+ resetState(); // In case we missed an UP/CANCEL event
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ updateStateByEvent(event);
+
+ // See if we have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if(!mSloppyGesture){
+ // No, start gesture now
+ mGestureInProgress = mListener.onRotateBegin(this);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ // See if we still have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if(!mSloppyGesture){
+ // No, start normal gesture now
+ mGestureInProgress = mListener.onRotateBegin(this);
+ }
+
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ break;
+ }
+ }
+
+
+ @Override
+ protected void handleInProgressEvent(int actionCode, MotionEvent event){
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_UP:
+ // Gesture ended but
+ updateStateByEvent(event);
+
+ if (!mSloppyGesture) {
+ mListener.onRotateEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (!mSloppyGesture) {
+ mListener.onRotateEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ updateStateByEvent(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit. This can help filter shaky data as a
+ // finger is lifted.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
+ final boolean updatePrevious = mListener.onRotate(this);
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void resetState() {
+ super.resetState();
+ mSloppyGesture = false;
+ }
+
+
+ /**
+ * Return the rotation difference from the previous rotate event to the current
+ * event.
+ *
+ * @return The current rotation //difference in degrees.
+ */
+ public float getRotationDegreesDelta() {
+ double diffRadians = Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX) - Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX);
+ return (float) (diffRadians * 180 / Math.PI);
+ }
+}
diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/ShoveGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/ShoveGestureDetector.java
new file mode 100644
index 0000000000..cb8f818709
--- /dev/null
+++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/ShoveGestureDetector.java
@@ -0,0 +1,202 @@
+package com.almeros.android.multitouch;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * @author Robert Nordan (robert.nordan@norkart.no)
+ *
+ * Copyright (c) 2013, Norkart AS
+ *
+ * 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.
+ */
+public class ShoveGestureDetector extends TwoFingerGestureDetector {
+
+ /**
+ * Listener which must be implemented which is used by ShoveGestureDetector
+ * to perform callbacks to any implementing class which is registered to a
+ * ShoveGestureDetector via the constructor.
+ *
+ * @see ShoveGestureDetector.SimpleOnShoveGestureListener
+ */
+ public interface OnShoveGestureListener {
+ public boolean onShove(ShoveGestureDetector detector);
+ public boolean onShoveBegin(ShoveGestureDetector detector);
+ public void onShoveEnd(ShoveGestureDetector detector);
+ }
+
+ /**
+ * Helper class which may be extended and where the methods may be
+ * implemented. This way it is not necessary to implement all methods
+ * of OnShoveGestureListener.
+ */
+ public static class SimpleOnShoveGestureListener implements OnShoveGestureListener {
+ public boolean onShove(ShoveGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onShoveBegin(ShoveGestureDetector detector) {
+ return true;
+ }
+
+ public void onShoveEnd(ShoveGestureDetector detector) {
+ // Do nothing, overridden implementation may be used
+ }
+ }
+
+ private float mPrevAverageY;
+ private float mCurrAverageY;
+
+ private final OnShoveGestureListener mListener;
+ private boolean mSloppyGesture;
+
+ public ShoveGestureDetector(Context context, OnShoveGestureListener listener) {
+ super(context);
+ mListener = listener;
+ }
+
+ @Override
+ protected void handleStartProgressEvent(int actionCode, MotionEvent event){
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // At least the second finger is on screen now
+
+ resetState(); // In case we missed an UP/CANCEL event
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ updateStateByEvent(event);
+
+ // See if we have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if(!mSloppyGesture){
+ // No, start gesture now
+ mGestureInProgress = mListener.onShoveBegin(this);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ // See if we still have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if(!mSloppyGesture){
+ // No, start normal gesture now
+ mGestureInProgress = mListener.onShoveBegin(this);
+ }
+
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ break;
+ }
+ }
+
+
+ @Override
+ protected void handleInProgressEvent(int actionCode, MotionEvent event){
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_UP:
+ // Gesture ended but
+ updateStateByEvent(event);
+
+ if (!mSloppyGesture) {
+ mListener.onShoveEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (!mSloppyGesture) {
+ mListener.onShoveEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ updateStateByEvent(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit. This can help filter shaky data as a
+ // finger is lifted. Also check that shove is meaningful.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD
+ && Math.abs(getShovePixelsDelta()) > 0.5f) {
+ final boolean updatePrevious = mListener.onShove(this);
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void resetState() {
+ super.resetState();
+ mSloppyGesture = false;
+ mPrevAverageY = 0.0f;
+ mCurrAverageY = 0.0f;
+ }
+
+ @Override
+ protected void updateStateByEvent(MotionEvent curr){
+ super.updateStateByEvent(curr);
+
+ final MotionEvent prev = mPrevEvent;
+ float py0 = prev.getY(0);
+ float py1 = prev.getY(1);
+ mPrevAverageY = (py0 + py1) / 2.0f;
+
+ float cy0 = curr.getY(0);
+ float cy1 = curr.getY(1);
+ mCurrAverageY = (cy0 + cy1) / 2.0f;
+ }
+
+ @Override
+ protected boolean isSloppyGesture(MotionEvent event){
+ boolean sloppy = super.isSloppyGesture(event);
+ if (sloppy)
+ return true;
+
+ // If it's not traditionally sloppy, we check if the angle between fingers
+ // is acceptable.
+ double angle = Math.abs(Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX));
+ //about 20 degrees, left or right
+ return !(( 0.0f < angle && angle < 0.35f)
+ || 2.79f < angle && angle < Math.PI);
+ }
+
+
+ /**
+ * Return the distance in pixels from the previous shove event to the current
+ * event.
+ *
+ * @return The current distance in pixels.
+ */
+ public float getShovePixelsDelta() {
+ return mCurrAverageY - mPrevAverageY;
+ }
+}
diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/TwoFingerGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/TwoFingerGestureDetector.java
new file mode 100644
index 0000000000..57e4e21d2d
--- /dev/null
+++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/TwoFingerGestureDetector.java
@@ -0,0 +1,179 @@
+package com.almeros.android.multitouch;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * @author Almer Thie (code.almeros.com)
+ * Copyright (c) 2013, Almer Thie (code.almeros.com)
+ *
+ * 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.
+ */
+public abstract class TwoFingerGestureDetector extends BaseGestureDetector {
+
+ private final float mEdgeSlop;
+ private float mRightSlopEdge;
+ private float mBottomSlopEdge;
+
+ protected float mPrevFingerDiffX;
+ protected float mPrevFingerDiffY;
+ protected float mCurrFingerDiffX;
+ protected float mCurrFingerDiffY;
+
+ private float mCurrLen;
+ private float mPrevLen;
+
+ public TwoFingerGestureDetector(Context context) {
+ super(context);
+
+ ViewConfiguration config = ViewConfiguration.get(context);
+ mEdgeSlop = config.getScaledEdgeSlop();
+ }
+
+ @Override
+ protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event);
+
+ @Override
+ protected abstract void handleInProgressEvent(int actionCode, MotionEvent event);
+
+ protected void updateStateByEvent(MotionEvent curr){
+ super.updateStateByEvent(curr);
+
+ final MotionEvent prev = mPrevEvent;
+
+ mCurrLen = -1;
+ mPrevLen = -1;
+
+ // Previous
+ final float px0 = prev.getX(0);
+ final float py0 = prev.getY(0);
+ final float px1 = prev.getX(1);
+ final float py1 = prev.getY(1);
+ final float pvx = px1 - px0;
+ final float pvy = py1 - py0;
+ mPrevFingerDiffX = pvx;
+ mPrevFingerDiffY = pvy;
+
+ // Current
+ final float cx0 = curr.getX(0);
+ final float cy0 = curr.getY(0);
+ final float cx1 = curr.getX(1);
+ final float cy1 = curr.getY(1);
+ final float cvx = cx1 - cx0;
+ final float cvy = cy1 - cy0;
+ mCurrFingerDiffX = cvx;
+ mCurrFingerDiffY = cvy;
+ }
+
+ /**
+ * Return the current distance between the two pointers forming the
+ * gesture in progress.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpan() {
+ if (mCurrLen == -1) {
+ final float cvx = mCurrFingerDiffX;
+ final float cvy = mCurrFingerDiffY;
+ mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy);
+ }
+ return mCurrLen;
+ }
+
+ /**
+ * Return the previous distance between the two pointers forming the
+ * gesture in progress.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpan() {
+ if (mPrevLen == -1) {
+ final float pvx = mPrevFingerDiffX;
+ final float pvy = mPrevFingerDiffY;
+ mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy);
+ }
+ return mPrevLen;
+ }
+
+ /**
+ * MotionEvent has no getRawX(int) method; simulate it pending future API approval.
+ * @param event
+ * @param pointerIndex
+ * @return
+ */
+ protected static float getRawX(MotionEvent event, int pointerIndex) {
+ float offset = event.getX() - event.getRawX();
+ if(pointerIndex < event.getPointerCount()){
+ return event.getX(pointerIndex) + offset;
+ }
+ return 0f;
+ }
+
+ /**
+ * MotionEvent has no getRawY(int) method; simulate it pending future API approval.
+ * @param event
+ * @param pointerIndex
+ * @return
+ */
+ protected static float getRawY(MotionEvent event, int pointerIndex) {
+ float offset = event.getY() - event.getRawY();
+ if(pointerIndex < event.getPointerCount()){
+ return event.getY(pointerIndex) + offset;
+ }
+ return 0f;
+ }
+
+ /**
+ * Check if we have a sloppy gesture. Sloppy gestures can happen if the edge
+ * of the user's hand is touching the screen, for example.
+ *
+ * @param event
+ * @return
+ */
+ protected boolean isSloppyGesture(MotionEvent event){
+ // As orientation can change, query the metrics in touch down
+ DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+ mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
+ mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
+
+ final float edgeSlop = mEdgeSlop;
+ final float rightSlop = mRightSlopEdge;
+ final float bottomSlop = mBottomSlopEdge;
+
+ final float x0 = event.getRawX();
+ final float y0 = event.getRawY();
+ final float x1 = getRawX(event, 1);
+ final float y1 = getRawY(event, 1);
+
+ boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
+ || x0 > rightSlop || y0 > bottomSlop;
+ boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
+ || x1 > rightSlop || y1 > bottomSlop;
+
+ if (p0sloppy && p1sloppy) {
+ return true;
+ } else if (p0sloppy) {
+ return true;
+ } else if (p1sloppy) {
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java
new file mode 100644
index 0000000000..720a1f541b
--- /dev/null
+++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java
@@ -0,0 +1,161 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
+ * (code.almeros.com)
+ *
+ * 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.
+ */
+public abstract class BaseGestureDetector {
+ protected final Context mContext;
+ protected boolean mGestureInProgress;
+
+ protected MotionEvent mPrevEvent;
+ protected MotionEvent mCurrEvent;
+
+ protected float mCurrPressure;
+ protected float mPrevPressure;
+ protected long mTimeDelta;
+
+ /**
+ * This value is the threshold ratio between the previous combined pressure
+ * and the current combined pressure. When pressure decreases rapidly
+ * between events the position values can often be imprecise, as it usually
+ * indicates that the user is in the process of lifting a pointer off of the
+ * device. This value was tuned experimentally.
+ */
+ protected static final float PRESSURE_THRESHOLD = 0.67f;
+
+ public BaseGestureDetector(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * All gesture detectors need to be called through this method to be able to
+ * detect gestures. This method delegates work to handler methods
+ * (handleStartProgressEvent, handleInProgressEvent) implemented in
+ * extending classes.
+ *
+ * @param event
+ * @return
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ final int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
+ if (!mGestureInProgress) {
+ handleStartProgressEvent(actionCode, event);
+ } else {
+ handleInProgressEvent(actionCode, event);
+ }
+ return true;
+ }
+
+ /**
+ * Called when the current event occurred when NO gesture is in progress
+ * yet. The handling in this implementation may set the gesture in progress
+ * (via mGestureInProgress) or out of progress
+ *
+ * @param actionCode
+ * @param event
+ */
+ protected abstract void handleStartProgressEvent(int actionCode,
+ MotionEvent event);
+
+ /**
+ * Called when the current event occurred when a gesture IS in progress. The
+ * handling in this implementation may set the gesture out of progress (via
+ * mGestureInProgress).
+ *
+ *
+ * @param actionCode
+ * @param event
+ */
+ protected abstract void handleInProgressEvent(int actionCode,
+ MotionEvent event);
+
+ protected void updateStateByEvent(MotionEvent curr) {
+ final MotionEvent prev = mPrevEvent;
+
+ // Reset mCurrEvent
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ mCurrEvent = MotionEvent.obtain(curr);
+
+ // Delta time
+ mTimeDelta = curr.getEventTime() - prev.getEventTime();
+
+ // Pressure
+ mCurrPressure = curr.getPressure(curr.getActionIndex());
+ mPrevPressure = prev.getPressure(prev.getActionIndex());
+ }
+
+ protected void resetState() {
+ if (mPrevEvent != null) {
+ mPrevEvent.recycle();
+ mPrevEvent = null;
+ }
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ mGestureInProgress = false;
+ }
+
+ /**
+ * Returns {@code true} if a gesture is currently in progress.
+ *
+ * @return {@code true} if a gesture is currently in progress, {@code false}
+ * otherwise.
+ */
+ public boolean isInProgress() {
+ return mGestureInProgress;
+ }
+
+ /**
+ * Return the time difference in milliseconds between the previous accepted
+ * GestureDetector event and the current GestureDetector event.
+ *
+ * @return Time difference since the last move event in milliseconds.
+ */
+ public long getTimeDelta() {
+ return mTimeDelta;
+ }
+
+ /**
+ * Return the event time of the current GestureDetector event being
+ * processed.
+ *
+ * @return Current GestureDetector event time in milliseconds.
+ */
+ public long getEventTime() {
+ return mCurrEvent.getEventTime();
+ }
+
+}
diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java
new file mode 100644
index 0000000000..2430f3f920
--- /dev/null
+++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java
@@ -0,0 +1,185 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+/**
+ * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
+ * (code.almeros.com)
+ *
+ * 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.
+ */
+public class MoveGestureDetector extends BaseGestureDetector {
+
+ /**
+ * Listener which must be implemented which is used by MoveGestureDetector
+ * to perform callbacks to any implementing class which is registered to a
+ * MoveGestureDetector via the constructor.
+ *
+ * @see MoveGestureDetector.SimpleOnMoveGestureListener
+ */
+ public interface OnMoveGestureListener {
+ public boolean onMove(MoveGestureDetector detector);
+
+ public boolean onMoveBegin(MoveGestureDetector detector);
+
+ public void onMoveEnd(MoveGestureDetector detector);
+ }
+
+ /**
+ * Helper class which may be extended and where the methods may be
+ * implemented. This way it is not necessary to implement all methods of
+ * OnMoveGestureListener.
+ */
+ public static class SimpleOnMoveGestureListener implements
+ OnMoveGestureListener {
+ public boolean onMove(MoveGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onMoveBegin(MoveGestureDetector detector) {
+ return true;
+ }
+
+ public void onMoveEnd(MoveGestureDetector detector) {
+ // Do nothing, overridden implementation may be used
+ }
+ }
+
+ private static final PointF FOCUS_DELTA_ZERO = new PointF();
+
+ private final OnMoveGestureListener mListener;
+
+ private PointF mFocusExternal = new PointF();
+ private PointF mFocusDeltaExternal = new PointF();
+
+ public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
+ super(context);
+ mListener = listener;
+ }
+
+ @Override
+ protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_DOWN:
+ resetState(); // In case we missed an UP/CANCEL event
+
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ updateStateByEvent(event);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ mGestureInProgress = mListener.onMoveBegin(this);
+ break;
+ }
+ }
+
+ @Override
+ protected void handleInProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mListener.onMoveEnd(this);
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ updateStateByEvent(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit. This can help filter shaky data as a
+ // finger is lifted.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
+ final boolean updatePrevious = mListener.onMove(this);
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+
+ protected void updateStateByEvent(MotionEvent curr) {
+ super.updateStateByEvent(curr);
+
+ final MotionEvent prev = mPrevEvent;
+
+ // Focus intenal
+ PointF mCurrFocusInternal = determineFocalPoint(curr);
+ PointF mPrevFocusInternal = determineFocalPoint(prev);
+
+ // Focus external
+ // - Prevent skipping of focus delta when a finger is added or removed
+ boolean mSkipNextMoveEvent = prev.getPointerCount() != curr
+ .getPointerCount();
+ mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO
+ : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x,
+ mCurrFocusInternal.y - mPrevFocusInternal.y);
+
+ // - Don't directly use mFocusInternal (or skipping will occur). Add
+ // unskipped delta values to mFocusExternal instead.
+ mFocusExternal.x += mFocusDeltaExternal.x;
+ mFocusExternal.y += mFocusDeltaExternal.y;
+ }
+
+ /**
+ * Determine (multi)finger focal point (a.k.a. center point between all
+ * fingers)
+ *
+ * @param e
+ * @return PointF focal point
+ */
+ private PointF determineFocalPoint(MotionEvent e) {
+ // Number of fingers on screen
+ final int pCount = e.getPointerCount();
+ float x = 0.0f;
+ float y = 0.0f;
+
+ for (int i = 0; i < pCount; i++) {
+ x += e.getX(i);
+ y += e.getY(i);
+ }
+
+ return new PointF(x / pCount, y / pCount);
+ }
+
+ public float getFocusX() {
+ return mFocusExternal.x;
+ }
+
+ public float getFocusY() {
+ return mFocusExternal.y;
+ }
+
+ public PointF getFocusDelta() {
+ return mFocusDeltaExternal;
+ }
+
+}
diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java
new file mode 100644
index 0000000000..124fe8509c
--- /dev/null
+++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java
@@ -0,0 +1,180 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
+ * (code.almeros.com)
+ *
+ * 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.
+ */
+public class RotateGestureDetector extends TwoFingerGestureDetector {
+
+ /**
+ * Listener which must be implemented which is used by RotateGestureDetector
+ * to perform callbacks to any implementing class which is registered to a
+ * RotateGestureDetector via the constructor.
+ *
+ * @see RotateGestureDetector.SimpleOnRotateGestureListener
+ */
+ public interface OnRotateGestureListener {
+ public boolean onRotate(RotateGestureDetector detector);
+
+ public boolean onRotateBegin(RotateGestureDetector detector);
+
+ public void onRotateEnd(RotateGestureDetector detector);
+ }
+
+ /**
+ * Helper class which may be extended and where the methods may be
+ * implemented. This way it is not necessary to implement all methods of
+ * OnRotateGestureListener.
+ */
+ public static class SimpleOnRotateGestureListener implements
+ OnRotateGestureListener {
+ public boolean onRotate(RotateGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onRotateBegin(RotateGestureDetector detector) {
+ return true;
+ }
+
+ public void onRotateEnd(RotateGestureDetector detector) {
+ // Do nothing, overridden implementation may be used
+ }
+ }
+
+ private final OnRotateGestureListener mListener;
+ private boolean mSloppyGesture;
+
+ public RotateGestureDetector(Context context,
+ OnRotateGestureListener listener) {
+ super(context);
+ mListener = listener;
+ }
+
+ @Override
+ protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // At least the second finger is on screen now
+
+ resetState(); // In case we missed an UP/CANCEL event
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ updateStateByEvent(event);
+
+ // See if we have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if (!mSloppyGesture) {
+ // No, start gesture now
+ mGestureInProgress = mListener.onRotateBegin(this);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ // See if we still have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if (!mSloppyGesture) {
+ // No, start normal gesture now
+ mGestureInProgress = mListener.onRotateBegin(this);
+ }
+
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ break;
+ }
+ }
+
+ @Override
+ protected void handleInProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_UP:
+ // Gesture ended but
+ updateStateByEvent(event);
+
+ if (!mSloppyGesture) {
+ mListener.onRotateEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (!mSloppyGesture) {
+ mListener.onRotateEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ updateStateByEvent(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit. This can help filter shaky data as a
+ // finger is lifted.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
+ final boolean updatePrevious = mListener.onRotate(this);
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void resetState() {
+ super.resetState();
+ mSloppyGesture = false;
+ }
+
+ /**
+ * Return the rotation difference from the previous rotate event to the
+ * current event.
+ *
+ * @return The current rotation //difference in degrees.
+ */
+ public float getRotationDegreesDelta() {
+ double diffRadians = Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX)
+ - Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX);
+ return (float) (diffRadians * 180.0 / Math.PI);
+ }
+}
diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java
new file mode 100644
index 0000000000..254597105b
--- /dev/null
+++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java
@@ -0,0 +1,213 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * @author Robert Nordan (robert.nordan@norkart.no)
+ *
+ * Copyright (c) 2013, Norkart AS
+ *
+ * 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.
+ */
+public class ShoveGestureDetector extends TwoFingerGestureDetector {
+
+ /**
+ * Listener which must be implemented which is used by ShoveGestureDetector
+ * to perform callbacks to any implementing class which is registered to a
+ * ShoveGestureDetector via the constructor.
+ *
+ * @see ShoveGestureDetector.SimpleOnShoveGestureListener
+ */
+ public interface OnShoveGestureListener {
+ public boolean onShove(ShoveGestureDetector detector);
+
+ public boolean onShoveBegin(ShoveGestureDetector detector);
+
+ public void onShoveEnd(ShoveGestureDetector detector);
+ }
+
+ /**
+ * Helper class which may be extended and where the methods may be
+ * implemented. This way it is not necessary to implement all methods of
+ * OnShoveGestureListener.
+ */
+ public static class SimpleOnShoveGestureListener implements
+ OnShoveGestureListener {
+ public boolean onShove(ShoveGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onShoveBegin(ShoveGestureDetector detector) {
+ return true;
+ }
+
+ public void onShoveEnd(ShoveGestureDetector detector) {
+ // Do nothing, overridden implementation may be used
+ }
+ }
+
+ private float mPrevAverageY;
+ private float mCurrAverageY;
+
+ private final OnShoveGestureListener mListener;
+ private boolean mSloppyGesture;
+
+ public ShoveGestureDetector(Context context, OnShoveGestureListener listener) {
+ super(context);
+ mListener = listener;
+ }
+
+ @Override
+ protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // At least the second finger is on screen now
+
+ resetState(); // In case we missed an UP/CANCEL event
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ updateStateByEvent(event);
+
+ // See if we have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if (!mSloppyGesture) {
+ // No, start gesture now
+ mGestureInProgress = mListener.onShoveBegin(this);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ // See if we still have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if (!mSloppyGesture) {
+ // No, start normal gesture now
+ mGestureInProgress = mListener.onShoveBegin(this);
+ }
+
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ break;
+ }
+ }
+
+ @Override
+ protected void handleInProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_UP:
+ // Gesture ended but
+ updateStateByEvent(event);
+
+ if (!mSloppyGesture) {
+ mListener.onShoveEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (!mSloppyGesture) {
+ mListener.onShoveEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ updateStateByEvent(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit. This can help filter shaky data as a
+ // finger is lifted. Also check that shove is meaningful.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD
+ && Math.abs(getShovePixelsDelta()) > 0.5f) {
+ final boolean updatePrevious = mListener.onShove(this);
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void resetState() {
+ super.resetState();
+ mSloppyGesture = false;
+ mPrevAverageY = 0.0f;
+ mCurrAverageY = 0.0f;
+ }
+
+ @Override
+ protected void updateStateByEvent(MotionEvent curr) {
+ super.updateStateByEvent(curr);
+
+ final MotionEvent prev = mPrevEvent;
+ float py0 = prev.getY(0);
+ float py1 = prev.getY(1);
+ mPrevAverageY = (py0 + py1) / 2.0f;
+
+ float cy0 = curr.getY(0);
+ float cy1 = curr.getY(1);
+ mCurrAverageY = (cy0 + cy1) / 2.0f;
+ }
+
+ @Override
+ protected boolean isSloppyGesture(MotionEvent event) {
+ boolean sloppy = super.isSloppyGesture(event);
+ if (sloppy)
+ return true;
+
+ // If it's not traditionally sloppy, we check if the angle between
+ // fingers
+ // is acceptable.
+ double angle = Math.abs(Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX));
+ // about 20 degrees, left or right
+ return !((0.0f < angle && angle < 0.35f) || 2.79f < angle
+ && angle < Math.PI);
+ }
+
+ /**
+ * Return the distance in pixels from the previous shove event to the
+ * current event.
+ *
+ * @return The current distance in pixels.
+ */
+ public float getShovePixelsDelta() {
+ return mCurrAverageY - mPrevAverageY;
+ }
+}
diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java
new file mode 100644
index 0000000000..e26d6b5ae4
--- /dev/null
+++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java
@@ -0,0 +1,223 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
+ * (code.almeros.com)
+ *
+ * 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.
+ */
+public abstract class TwoFingerGestureDetector extends BaseGestureDetector {
+
+ private final float mEdgeSlop;
+
+ protected float mPrevFingerDiffX;
+ protected float mPrevFingerDiffY;
+ protected float mCurrFingerDiffX;
+ protected float mCurrFingerDiffY;
+
+ private float mCurrLen;
+ private float mPrevLen;
+
+ private PointF mFocus;
+
+ public TwoFingerGestureDetector(Context context) {
+ super(context);
+
+ ViewConfiguration config = ViewConfiguration.get(context);
+ mEdgeSlop = config.getScaledEdgeSlop();
+ }
+
+ @Override
+ protected abstract void handleStartProgressEvent(int actionCode,
+ MotionEvent event);
+
+ @Override
+ protected abstract void handleInProgressEvent(int actionCode,
+ MotionEvent event);
+
+ protected void updateStateByEvent(MotionEvent curr) {
+ super.updateStateByEvent(curr);
+
+ final MotionEvent prev = mPrevEvent;
+
+ mCurrLen = -1;
+ mPrevLen = -1;
+
+ // Previous
+ final float px0 = prev.getX(0);
+ final float py0 = prev.getY(0);
+ final float px1 = prev.getX(1);
+ final float py1 = prev.getY(1);
+ final float pvx = px1 - px0;
+ final float pvy = py1 - py0;
+ mPrevFingerDiffX = pvx;
+ mPrevFingerDiffY = pvy;
+
+ // Current
+ final float cx0 = curr.getX(0);
+ final float cy0 = curr.getY(0);
+ final float cx1 = curr.getX(1);
+ final float cy1 = curr.getY(1);
+ final float cvx = cx1 - cx0;
+ final float cvy = cy1 - cy0;
+ mCurrFingerDiffX = cvx;
+ mCurrFingerDiffY = cvy;
+ mFocus = determineFocalPoint(curr);
+ }
+
+ /**
+ * Return the current distance between the two pointers forming the gesture
+ * in progress.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpan() {
+ if (mCurrLen == -1) {
+ final float cvx = mCurrFingerDiffX;
+ final float cvy = mCurrFingerDiffY;
+ mCurrLen = (float) Math.sqrt(cvx * cvx + cvy * cvy);
+ }
+ return mCurrLen;
+ }
+
+ /**
+ * Return the previous distance between the two pointers forming the gesture
+ * in progress.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpan() {
+ if (mPrevLen == -1) {
+ final float pvx = mPrevFingerDiffX;
+ final float pvy = mPrevFingerDiffY;
+ mPrevLen = (float) Math.sqrt(pvx * pvx + pvy * pvy);
+ }
+ return mPrevLen;
+ }
+
+ /**
+ * MotionEvent has no getRawX(int) method; simulate it pending future API
+ * approval.
+ *
+ * @param event
+ * @param pointerIndex
+ * @return
+ */
+ protected static float getRawX(MotionEvent event, int pointerIndex) {
+ float offset = event.getX() - event.getRawX();
+ if (pointerIndex < event.getPointerCount()) {
+ return event.getX(pointerIndex) + offset;
+ }
+ return 0.0f;
+ }
+
+ /**
+ * MotionEvent has no getRawY(int) method; simulate it pending future API
+ * approval.
+ *
+ * @param event
+ * @param pointerIndex
+ * @return
+ */
+ protected static float getRawY(MotionEvent event, int pointerIndex) {
+ float offset = event.getY() - event.getRawY();
+ if (pointerIndex < event.getPointerCount()) {
+ return event.getY(pointerIndex) + offset;
+ }
+ return 0.0f;
+ }
+
+ /**
+ * Check if we have a sloppy gesture. Sloppy gestures can happen if the edge
+ * of the user's hand is touching the screen, for example.
+ *
+ * @param event
+ * @return
+ */
+ protected boolean isSloppyGesture(MotionEvent event) {
+ // As orientation can change, query the metrics in touch down
+ DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+ float mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
+ float mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
+
+ final float edgeSlop = mEdgeSlop;
+
+ final float x0 = event.getRawX();
+ final float y0 = event.getRawY();
+ final float x1 = getRawX(event, 1);
+ final float y1 = getRawY(event, 1);
+
+ boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop || x0 > mRightSlopEdge
+ || y0 > mBottomSlopEdge;
+ boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop || x1 > mRightSlopEdge
+ || y1 > mBottomSlopEdge;
+
+ if (p0sloppy && p1sloppy) {
+ return true;
+ } else if (p0sloppy) {
+ return true;
+ } else if (p1sloppy) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determine (multi)finger focal point (a.k.a. center point between all
+ * fingers)
+ *
+ * @param e
+ * @return PointF focal point
+ */
+ public static PointF determineFocalPoint(MotionEvent e) {
+ // Number of fingers on screen
+ final int pCount = e.getPointerCount();
+ float x = 0.0f;
+ float y = 0.0f;
+
+ for (int i = 0; i < pCount; i++) {
+ x += e.getX(i);
+ y += e.getY(i);
+ }
+
+ return new PointF(x / pCount, y / pCount);
+ }
+
+ public float getFocusX() {
+ return mFocus.x;
+ }
+
+ public float getFocusY() {
+ return mFocus.y;
+ }
+
+}
diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLat.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLat.java
new file mode 100644
index 0000000000..a36548fe3b
--- /dev/null
+++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLat.java
@@ -0,0 +1,87 @@
+package com.mapbox.mapboxgl.lib;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class LonLat implements Parcelable {
+
+ public static final Parcelable.Creator<LonLat> CREATOR = new Parcelable.Creator<LonLat>() {
+ public LonLat createFromParcel(Parcel in) {
+ return new LonLat(in);
+ }
+
+ public LonLat[] newArray(int size) {
+ return new LonLat[size];
+ }
+ };
+
+ private double lon;
+ private double lat;
+
+ public LonLat(double lon, double lat) {
+ this.lon = lon;
+ this.lat = lat;
+ }
+
+ protected LonLat(Parcel in) {
+ lon = in.readDouble();
+ lat = in.readDouble();
+ }
+
+ public double getLon() {
+ return lon;
+ }
+
+ public void setLon(double lon) {
+ this.lon = lon;
+ }
+
+ public double getLat() {
+ return lat;
+ }
+
+ public void setLat(double lat) {
+ this.lat = lat;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ long temp;
+ temp = Double.doubleToLongBits(lat);
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(lon);
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ LonLat other = (LonLat) obj;
+ return Double.doubleToLongBits(lat) == Double.doubleToLongBits(other.lat) && Double.doubleToLongBits(lon) == Double.doubleToLongBits(other.lon);
+ }
+
+ @Override
+ public String toString() {
+ return "LonLat [lon=" + lon + ", lat=" + lat + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeDouble(lon);
+ out.writeDouble(lat);
+ }
+
+}
diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLatZoom.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLatZoom.java
new file mode 100644
index 0000000000..fd78e9356e
--- /dev/null
+++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLatZoom.java
@@ -0,0 +1,82 @@
+package com.mapbox.mapboxgl.lib;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class LonLatZoom extends LonLat implements Parcelable {
+
+ public static final Parcelable.Creator<LonLatZoom> CREATOR = new Parcelable.Creator<LonLatZoom>() {
+ public LonLatZoom createFromParcel(Parcel in) {
+ return new LonLatZoom(in);
+ }
+
+ public LonLatZoom[] newArray(int size) {
+ return new LonLatZoom[size];
+ }
+ };
+
+ private double zoom;
+
+ public LonLatZoom(double lon, double lat, double zoom) {
+ super(lon, lat);
+ this.zoom = zoom;
+ }
+
+ public LonLatZoom(LonLat lonLat, double zoom) {
+ super(lonLat.getLon(), lonLat.getLat());
+ this.zoom = zoom;
+ }
+
+ private LonLatZoom(Parcel in) {
+ super(in);
+ zoom = in.readDouble();
+ }
+
+ public double getZoom() {
+ return zoom;
+ }
+
+ public void setZoom(double zoom) {
+ this.zoom = zoom;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ long temp;
+ temp = super.hashCode();
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ LonLatZoom other = (LonLatZoom) obj;
+ return super.equals(obj) && Double.doubleToLongBits(zoom) == Double.doubleToLongBits(other.zoom);
+ }
+
+ @Override
+ public String toString() {
+ return "LonLatZoom [lon=" + super.getLon() + ", lat=" + super.getLat() + ", zoom=" + zoom
+ + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeDouble(zoom);
+ }
+
+}
diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java
new file mode 100644
index 0000000000..8985c318bb
--- /dev/null
+++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java
@@ -0,0 +1,1212 @@
+package com.mapbox.mapboxgl.lib;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
+import android.graphics.PointF;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.view.GestureDetectorCompat;
+import android.support.v4.view.ScaleGestureDetectorCompat;
+import android.view.GestureDetector;
+import android.view.ScaleGestureDetector;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.ZoomButtonsController;
+
+import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector;
+import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector;
+
+import org.apache.commons.validator.routines.UrlValidator;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UnknownFormatConversionException;
+
+// Custom view that shows a Map
+// Based on SurfaceView as we use OpenGL ES to render
+public class MapView extends SurfaceView {
+
+ //
+ // Static members
+ //
+
+ // Tag used for logging
+ private static final String TAG = "MapView";
+
+ // Used for saving instance state
+ private static final String STATE_CENTER_COORDINATE = "centerCoordinate";
+ private static final String STATE_CENTER_DIRECTION = "centerDirection";
+ private static final String STATE_ZOOM_LEVEL = "zoomLevel";
+ private static final String STATE_DIRECTION = "direction";
+ private static final String STATE_ZOOM_ENABLED = "zoomEnabled";
+ private static final String STATE_SCROLL_ENABLED = "scrollEnabled";
+ private static final String STATE_ROTATE_ENABLED = "rotateEnabled";
+ private static final String STATE_DEBUG_ACTIVE = "debugActive";
+ private static final String STATE_STYLE_URL = "styleUrl";
+ private static final String STATE_ACCESS_TOKEN = "accessToken";
+ private static final String STATE_APPLIED_CLASSES = "appliedClasses";
+ private static final String STATE_DEFAULT_TRANSITION_DURATION = "defaultTransitionDuration";
+
+ //
+ // Instance members
+ //
+
+ // Used to call JNI NativeMapView
+ private NativeMapView mNativeMapView;
+
+ // Used to handle DPI scaling
+ private float mScreenDensity = 1.0f;
+
+ // Touch gesture detectors
+ private GestureDetectorCompat mGestureDetector;
+ private ScaleGestureDetector mScaleGestureDetector;
+ private RotateGestureDetector mRotateGestureDetector;
+ private boolean mTwoTap = false;
+
+ // Shows zoom buttons
+ private ZoomButtonsController mZoomButtonsController;
+
+ // Used to track trackball long presses
+ private TrackballLongPressTimeOut mCurrentTrackballLongPressTimeOut;
+
+ private ConnectivityReceiver mConnectivityReceiver;
+
+ // Holds the context
+ private Context mContext;
+
+ //
+ // Properties
+ //
+
+ private boolean mZoomEnabled = true;
+ private boolean mScrollEnabled = true;
+ private boolean mRotateEnabled = true;
+ private String mStyleUrl;
+
+ //
+ // Constructors
+ //
+
+ // Called when no properties are being set from XML
+ public MapView(Context context) {
+ super(context);
+ initialize(context, null);
+ }
+
+ // Called when properties are being set from XML
+ public MapView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize(context, attrs);
+ }
+
+ // Called when properties are being set from XML
+ public MapView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initialize(context, attrs);
+ }
+
+ //
+ // Initialization
+ //
+
+ // Common initialization code goes here
+ private void initialize(Context context, AttributeSet attrs) {
+ Log.v(TAG, "initialize");
+
+ // Save the context
+ mContext = context;
+
+ // Check if we are in Eclipse UI editor
+ if (isInEditMode()) {
+ // TODO editor does not load properly because we don't implement this
+ return;
+ }
+
+ // Get the screen's density
+ mScreenDensity = context.getResources().getDisplayMetrics().density;
+
+ // Get the cache path
+ String cachePath = context.getCacheDir().getAbsolutePath();
+ String dataPath = context.getFilesDir().getAbsolutePath();
+ String apkPath = context.getPackageCodePath();
+
+ // Create the NativeMapView
+ mNativeMapView = new NativeMapView(this, cachePath, dataPath, apkPath);
+
+ // Load the attributes
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView, 0, 0);
+ try {
+ double centerLongitude = typedArray.getFloat(R.styleable.MapView_centerLongitude, 0.0f);
+ double centerLatitude = typedArray.getFloat(R.styleable.MapView_centerLatitude, 0.0f);
+ LonLat centerCoordinate = new LonLat(centerLongitude, centerLatitude);
+ setCenterCoordinate(centerCoordinate);
+ setZoomLevel(typedArray.getFloat(R.styleable.MapView_zoomLevel, 0.0f)); // need to set zoom level first because of limitation on rotating when zoomed out
+ setDirection(typedArray.getFloat(R.styleable.MapView_direction, 0.0f));
+ setZoomEnabled(typedArray.getBoolean(R.styleable.MapView_zoomEnabled, true));
+ setScrollEnabled(typedArray.getBoolean(R.styleable.MapView_scrollEnabled, true));
+ setRotateEnabled(typedArray.getBoolean(R.styleable.MapView_rotateEnabled, true));
+ setDebugActive(typedArray.getBoolean(R.styleable.MapView_debugActive, false));
+ if (typedArray.getString(R.styleable.MapView_styleUrl) != null) {
+ setStyleUrl(typedArray.getString(R.styleable.MapView_styleUrl));
+ }
+ } finally {
+ typedArray.recycle();
+ }
+
+ // Ensure this view is interactable
+ setClickable(true);
+ setLongClickable(true);
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ requestFocus();
+
+ // Register the SurfaceHolder callbacks
+ getHolder().addCallback(new Callbacks());
+
+ // Touch gesture detectors
+ mGestureDetector = new GestureDetectorCompat(context, new GestureListener());
+ mGestureDetector.setIsLongpressEnabled(true);
+ mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
+ ScaleGestureDetectorCompat.setQuickScaleEnabled(mScaleGestureDetector, true);
+ mRotateGestureDetector = new RotateGestureDetector(context, new RotateGestureListener());
+
+ // Shows the zoom controls
+ // But not when in Eclipse UI editor
+ if (!isInEditMode()) {
+ if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) {
+ mZoomButtonsController = new ZoomButtonsController(this);
+ mZoomButtonsController.setZoomSpeed(300);
+ mZoomButtonsController.setOnZoomListener(new OnZoomListener());
+ }
+
+ // Check current connection status
+ ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
+ boolean isConnected = (activeNetwork != null) && activeNetwork.isConnectedOrConnecting();
+ onConnectivityChanged(isConnected);
+ }
+ }
+
+ //
+ // Property methods
+ //
+
+ public LonLat getCenterCoordinate() {
+ return mNativeMapView.getLonLat();
+ }
+
+ public void setCenterCoordinate(LonLat centerCoordinate) {
+ setCenterCoordinate(centerCoordinate, false);
+ }
+
+ public void setCenterCoordinate(LonLat centerCoordinate, boolean animated) {
+ double duration = animated ? 0.3 : 0.0;
+ mNativeMapView.setLonLat(centerCoordinate, duration);
+ }
+
+ public void setCenterCoordinate(LonLatZoom centerCoordinate) {
+ setCenterCoordinate(centerCoordinate, false);
+ }
+
+ public void setCenterCoordinate(LonLatZoom centerCoordinate,
+ boolean animated) {
+ double duration = animated ? 0.3 : 0.0;
+ mNativeMapView.setLonLatZoom(centerCoordinate, duration);
+ }
+
+ public double getDirection() {
+ double direction = -mNativeMapView.getBearing();
+
+ while (direction > 360)
+ direction -= 360;
+ while (direction < 0)
+ direction += 360;
+
+ return direction;
+ }
+
+ public void setDirection(double direction) {
+ setDirection(direction, false);
+ }
+
+ public void setDirection(double direction, boolean animated) {
+ double duration = animated ? 0.3 : 0.0;
+
+ mNativeMapView.setBearing(-direction, duration);
+ }
+
+ public void resetPosition() {
+ mNativeMapView.resetPosition();
+ }
+
+ public void resetNorth() {
+ mNativeMapView.resetNorth();
+ }
+
+ public double getZoomLevel() {
+ return mNativeMapView.getZoom();
+ }
+
+ public void setZoomLevel(double zoomLevel) {
+ setZoomLevel(zoomLevel, false);
+ }
+
+ public void setZoomLevel(double zoomLevel, boolean animated) {
+ double duration = animated ? 0.3 : 0.0;
+ mNativeMapView.setZoom(zoomLevel, duration);
+ }
+
+ public boolean isZoomEnabled() {
+ return mZoomEnabled;
+ }
+
+ public void setZoomEnabled(boolean zoomEnabled) {
+ this.mZoomEnabled = zoomEnabled;
+
+ if ((mZoomButtonsController != null) && (getVisibility() == View.VISIBLE) && mZoomEnabled) {
+ mZoomButtonsController.setVisible(true);
+ }
+ }
+
+ public boolean isScrollEnabled() {
+ return mScrollEnabled;
+ }
+
+ public void setScrollEnabled(boolean scrollEnabled) {
+ this.mScrollEnabled = scrollEnabled;
+ }
+
+ public boolean isRotateEnabled() {
+ return mRotateEnabled;
+ }
+
+ public void setRotateEnabled(boolean rotateEnabled) {
+ this.mRotateEnabled = rotateEnabled;
+ }
+
+ public boolean isDebugActive() {
+ return mNativeMapView.getDebug();
+ }
+
+ public void setDebugActive(boolean debugActive) {
+ mNativeMapView.setDebug(debugActive);
+ }
+
+ public void toggleDebug() {
+ mNativeMapView.toggleDebug();
+ }
+
+ private void validateStyleUrl(String url) {
+ String[] schemes = {"http", "https", "file", "asset"};
+ UrlValidator urlValidator = new UrlValidator(schemes, UrlValidator.NO_FRAGMENTS | UrlValidator.ALLOW_LOCAL_URLS);
+ if (!urlValidator.isValid(url)) {
+ throw new RuntimeException("Style URL is not a valid http, https, file or asset URL.");
+ }
+ }
+
+ public void setStyleUrl(String url) {
+ mStyleUrl = url;
+ mNativeMapView.setStyleUrl(url);
+ }
+
+ public String getStyleUrl() {
+ return mStyleUrl;
+ }
+
+ private void validateAccessToken(String accessToken) {
+
+ if (accessToken.isEmpty() | (!accessToken.startsWith("pk.") && !accessToken.startsWith("sk."))) {
+ throw new RuntimeException("Using MapView requires setting a valid access token. See the README.md");
+ }
+ }
+
+ public void setAccessToken(String accessToken) {
+ validateAccessToken(accessToken);
+ mNativeMapView.setAccessToken(accessToken);
+ }
+
+ public String getAccessToken() {
+ return mNativeMapView.getAccessToken();
+ }
+
+ //
+ // Style methods
+ //
+
+ public List<String> getAppliedStyleClasses() {
+ return mNativeMapView.getAppliedClasses();
+ }
+
+ public void setAppliedStyleClasses(List<String> styleClasses) {
+ setAppliedStyleClasses(styleClasses, 0);
+ }
+
+ public void setAppliedStyleClasses(List<String> styleClasses, long transitionDuration) {
+ mNativeMapView.setDefaultTransitionDuration(transitionDuration);
+ mNativeMapView.setAppliedClasses(styleClasses);
+ }
+
+ //
+ // Lifecycle events
+ //
+
+ // Called when we need to restore instance state
+ // Must be called from Activity onCreate
+ public void onCreate(Bundle savedInstanceState) {
+ Log.v(TAG, "onCreate");
+ if (savedInstanceState != null) {
+ setCenterCoordinate((LonLat) savedInstanceState.getParcelable(STATE_CENTER_COORDINATE));
+ setZoomLevel(savedInstanceState.getDouble(STATE_ZOOM_LEVEL)); // need to set zoom level first because of limitation on rotating when zoomed out
+ setDirection(savedInstanceState.getDouble(STATE_CENTER_DIRECTION));
+ setDirection(savedInstanceState.getDouble(STATE_DIRECTION));
+ setZoomEnabled(savedInstanceState.getBoolean(STATE_ZOOM_ENABLED));
+ setScrollEnabled(savedInstanceState.getBoolean(STATE_SCROLL_ENABLED));
+ setRotateEnabled(savedInstanceState.getBoolean(STATE_ROTATE_ENABLED));
+ setDebugActive(savedInstanceState.getBoolean(STATE_DEBUG_ACTIVE));
+ setStyleUrl(savedInstanceState.getString(STATE_STYLE_URL));
+ setAccessToken(savedInstanceState.getString(STATE_ACCESS_TOKEN));
+ List<String> appliedClasses = savedInstanceState.getStringArrayList(STATE_APPLIED_CLASSES);
+ if (!appliedClasses.isEmpty()) {
+ setAppliedStyleClasses(appliedClasses);
+ }
+ mNativeMapView.setDefaultTransitionDuration(savedInstanceState.getLong(STATE_DEFAULT_TRANSITION_DURATION));
+ }
+
+ validateAccessToken(getAccessToken());
+
+ mNativeMapView.initializeDisplay();
+ mNativeMapView.initializeContext();
+ mNativeMapView.start();
+ }
+
+ // Called when we need to save instance state
+ // Must be called from Activity onSaveInstanceState
+ public void onSaveInstanceState(Bundle outState) {
+ Log.v(TAG, "onSaveInstanceState");
+ outState.putParcelable(STATE_CENTER_COORDINATE, getCenterCoordinate());
+ outState.putDouble(STATE_ZOOM_LEVEL, getZoomLevel()); // need to set zoom level first because of limitation on rotating when zoomed out
+ outState.putDouble(STATE_CENTER_DIRECTION, getDirection());
+ outState.putBoolean(STATE_ZOOM_ENABLED, isZoomEnabled());
+ outState.putBoolean(STATE_SCROLL_ENABLED, isScrollEnabled());
+ outState.putBoolean(STATE_ROTATE_ENABLED, isRotateEnabled());
+ outState.putBoolean(STATE_DEBUG_ACTIVE, isDebugActive());
+ outState.putString(STATE_STYLE_URL, getStyleUrl());
+ outState.putString(STATE_ACCESS_TOKEN, getAccessToken());
+ outState.putStringArrayList(STATE_APPLIED_CLASSES, new ArrayList<String>(getAppliedStyleClasses()));
+ outState.putLong(STATE_DEFAULT_TRANSITION_DURATION, mNativeMapView.getDefaultTransitionDuration());
+ }
+
+ // Called when we need to clean up
+ // Must be called from Activity onDestroy
+ public void onDestroy() {
+ Log.v(TAG, "onDestroy");
+ mNativeMapView.stop();
+ mNativeMapView.terminateContext();
+ mNativeMapView.terminateDisplay();
+ }
+
+ // Called when we need to create the GL context
+ // Must be called from Activity onStart
+ public void onStart() {
+ Log.v(TAG, "onStart");
+ }
+
+ // Called when we need to terminate the GL context
+ // Must be called from Activity onPause
+ public void onStop() {
+ Log.v(TAG, "onStop");
+ //mNativeMapView.stop();
+ }
+
+ // Called when we need to stop the render thread
+ // Must be called from Activity onPause
+ public void onPause() {
+ Log.v(TAG, "onPause");
+
+ // Register for connectivity changes
+ getContext().unregisterReceiver(mConnectivityReceiver);
+ mConnectivityReceiver = null;
+
+ mNativeMapView.pause();
+ }
+
+ // Called when we need to start the render thread
+ // Must be called from Activity onResume
+
+ public void onResume() {
+ Log.v(TAG, "onResume");
+
+ // Register for connectivity changes
+ mConnectivityReceiver = new ConnectivityReceiver();
+ mContext.registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+
+ mNativeMapView.resume();
+ }
+
+ // This class handles SurfaceHolder callbacks
+ private class Callbacks implements SurfaceHolder.Callback2 {
+
+ // Called when we need to redraw the view
+ // This is called before our view is first visible to prevent an initial
+ // flicker (see Android SDK documentation)
+ @Override
+ public void surfaceRedrawNeeded(SurfaceHolder holder) {
+ Log.v(TAG, "surfaceRedrawNeeded");
+ mNativeMapView.update();
+ }
+
+ // Called when the native surface buffer has been created
+ // Must do all EGL/GL ES initialization here
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ Log.v(TAG, "surfaceCreated");
+ mNativeMapView.createSurface(holder.getSurface());
+ }
+
+ // Called when the native surface buffer has been destroyed
+ // Must do all EGL/GL ES destruction here
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ Log.v(TAG, "surfaceDestroyed");
+ mNativeMapView.destroySurface();
+ }
+
+ // Called when the format or size of the native surface buffer has been
+ // changed
+ // Must handle window resizing here.
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width,
+ int height) {
+ Log.v(TAG, "surfaceChanged");
+ Log.i(TAG, "resize " + format + " " + width + " " + height);
+ mNativeMapView.resize((int) (width / mScreenDensity), (int) (height / mScreenDensity), mScreenDensity, width, height);
+ }
+ }
+
+ // TODO examine how GLSurvaceView hadles attach/detach from window
+
+ // Called when view is no longer connected
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ // Required by ZoomButtonController (from Android SDK documentation)
+ if (mZoomButtonsController != null) {
+ mZoomButtonsController.setVisible(false);
+ }
+ }
+
+ // Called when view is hidden and shown
+ @Override
+ protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
+ // Required by ZoomButtonController (from Android SDK documentation)
+ if ((mZoomButtonsController != null) && (visibility != View.VISIBLE)) {
+ mZoomButtonsController.setVisible(false);
+ }
+ if ((mZoomButtonsController != null) && (visibility == View.VISIBLE)
+ && mZoomEnabled) {
+ mZoomButtonsController.setVisible(true);
+ }
+ }
+
+ //
+ // Draw events
+ //
+
+ // TODO: onDraw for UI editor mockup?
+ // By default it just shows a gray screen with "MapView"
+ // Not too important but perhaps we could put a static demo map image there
+
+ //
+ // Input events
+ //
+
+ // Zoom in or out
+ private void zoom(boolean zoomIn) {
+ zoom(zoomIn, -1.0f, -1.0f);
+ }
+
+ private void zoom(boolean zoomIn, float x, float y) {
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ if (zoomIn) {
+ mNativeMapView.scaleBy(2.0, x / mScreenDensity, y / mScreenDensity, 0.3);
+ } else {
+ // TODO two finger tap zoom out
+ mNativeMapView.scaleBy(0.5, x / mScreenDensity, y / mScreenDensity, 0.3);
+ }
+ }
+
+ // Called when user touches the screen, all positions are absolute
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ // Check and ignore non touch or left clicks
+ if ((event.getButtonState() != 0) && (event.getButtonState() != MotionEvent.BUTTON_PRIMARY)) {
+ return false;
+ }
+
+ // Check two finger gestures first
+ mRotateGestureDetector.onTouchEvent(event);
+ mScaleGestureDetector.onTouchEvent(event);
+
+ // Handle two finger tap
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ // First pointer down
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // Second pointer down
+ mTwoTap = event.getPointerCount() == 2;
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ // Second pointer up
+ break;
+
+ case MotionEvent.ACTION_UP:
+ // First pointer up
+ long tapInterval = event.getEventTime() - event.getDownTime();
+ boolean isTap = tapInterval <= ViewConfiguration.getTapTimeout();
+ boolean inProgress = mRotateGestureDetector.isInProgress() || mScaleGestureDetector.isInProgress();
+
+ if (mTwoTap && isTap && !inProgress) {
+ PointF focalPoint = TwoFingerGestureDetector.determineFocalPoint(event);
+ zoom(false, focalPoint.x, focalPoint.y);
+ mTwoTap = false;
+ return true;
+ }
+
+ mTwoTap = false;
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ mTwoTap = false;
+ break;
+ }
+
+ boolean retVal = mGestureDetector.onTouchEvent(event);
+ return retVal || super.onTouchEvent(event);
+ }
+
+ // This class handles one finger gestures
+ private class GestureListener extends
+ GestureDetector.SimpleOnGestureListener {
+
+ // Must always return true otherwise all events are ignored
+ @Override
+ public boolean onDown(MotionEvent e) {
+ // Show the zoom controls
+ if ((mZoomButtonsController != null) && mZoomEnabled) {
+ mZoomButtonsController.setVisible(true);
+ }
+
+ return true;
+ }
+
+ // Called for double taps
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ // Single finger double tap
+ // Zoom in
+ zoom(true, e.getX(), e.getY());
+ return true;
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ return true;
+ }
+
+ // Called for single taps after a delay
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ return false;
+ }
+
+ // Called for a long press
+ @Override
+ public void onLongPress(MotionEvent e) {
+ // TODO
+ }
+
+ // Called for flings
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Fling the map
+ // TODO Google Maps also has a rotate and zoom fling
+
+ float ease = 0.25f;
+
+ velocityX = velocityX * ease;
+ velocityY = velocityY * ease;
+
+ double speed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
+ double deceleration = 2500;
+ double duration = speed / (deceleration * ease);
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ mNativeMapView.moveBy(velocityX * duration / 2.0 / mScreenDensity, velocityY * duration / 2.0 / mScreenDensity, duration);
+
+ return true;
+ }
+
+ // Called for drags
+ // TODO use MoveGestureDetector
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions(); // TODO need to test canceling
+ // transitions with touch
+
+ // Scroll the map
+ mNativeMapView.moveBy(-distanceX / mScreenDensity, -distanceY / mScreenDensity);
+ return true;
+ }
+ }
+
+ // This class handles two finger gestures
+ private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+
+ long mBeginTime = 0;
+ float mScaleFactor = 1.0f;
+ boolean mStarted = false;
+
+ // Called when two fingers first touch the screen
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ mBeginTime = detector.getEventTime();
+
+ return true;
+ }
+
+ // Called when fingers leave screen
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ mBeginTime = 0;
+ mScaleFactor = 1.0f;
+ mStarted = false;
+ }
+
+ // Called each time one of the two fingers moves
+ // Called for pinch zooms
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ // If scale is large enough ignore a tap
+ // TODO: Google Maps seem to use a velocity rather than absolute
+ // value?
+ mScaleFactor *= detector.getScaleFactor();
+ if ((mScaleFactor > 1.05f) || (mScaleFactor < 0.95f)) {
+ mStarted = true;
+ }
+
+ // Ignore short touches in case it is a tap
+ // Also ignore small scales
+ long time = detector.getEventTime();
+ long interval = time - mBeginTime;
+ if (!mStarted && (interval <= ViewConfiguration.getTapTimeout())) {
+ return false;
+ }
+
+ // TODO complex decision between roate or scale or both (see Google
+ // Maps app)
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Scale the map
+ mNativeMapView.scaleBy(detector.getScaleFactor(), detector.getFocusX() / mScreenDensity, detector.getFocusY() / mScreenDensity);
+
+ return true;
+ }
+ }
+
+ // This class handles two finger rotate gestures
+ // TODO need way to single finger rotate - need to research how google maps
+ // does this - for phones with single touch, or when using mouse etc
+ private class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
+
+ long mBeginTime = 0;
+ float mTotalAngle = 0.0f;
+ boolean mStarted = false;
+
+ // Called when two fingers first touch the screen
+ @Override
+ public boolean onRotateBegin(RotateGestureDetector detector) {
+ if (!mRotateEnabled) {
+ return false;
+ }
+
+ mBeginTime = detector.getEventTime();
+ return true;
+ }
+
+ // Called when the fingers leave the screen
+ @Override
+ public void onRotateEnd(RotateGestureDetector detector) {
+ mBeginTime = 0;
+ mTotalAngle = 0.0f;
+ mStarted = false;
+ }
+
+ // Called each time one of the two fingers moves
+ // Called for rotation
+ @Override
+ public boolean onRotate(RotateGestureDetector detector) {
+ if (!mRotateEnabled) {
+ return false;
+ }
+
+ // If rotate is large enough ignore a tap
+ // TODO: Google Maps seem to use a velocity rather than absolute
+ // value, up to a point then they always rotate
+ mTotalAngle += detector.getRotationDegreesDelta();
+ if ((mTotalAngle > 5.0f) || (mTotalAngle < -5.0f)) {
+ mStarted = true;
+ }
+
+ // Ignore short touches in case it is a tap
+ // Also ignore small rotate
+ long time = detector.getEventTime();
+ long interval = time - mBeginTime;
+ if (!mStarted && (interval <= ViewConfiguration.getTapTimeout())) {
+ return false;
+ }
+
+ // TODO complex decision between rotate or scale or both (see Google
+ // Maps app). It seems if you start one or the other it takes more
+ // to start the other too. Haven't figured out what it uses to
+ // decide when to transition to both at the same time.
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Rotate the map
+ double bearing = mNativeMapView.getBearing();
+ bearing += detector.getRotationDegreesDelta();
+ mNativeMapView.setBearing(bearing, detector.getFocusX() / mScreenDensity, detector.getFocusY() / mScreenDensity);
+
+ return true;
+ }
+ }
+
+ // This class handles input events from the zoom control buttons
+ // Zoom controls allow single touch only devices to zoom in and out
+ private class OnZoomListener implements ZoomButtonsController.OnZoomListener {
+
+ // Not used
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ // Ignore
+ }
+
+ // Called when user pushes a zoom button
+ @Override
+ public void onZoom(boolean zoomIn) {
+ if (!mZoomEnabled) {
+ return;
+ }
+
+ // Zoom in or out
+ zoom(zoomIn);
+ }
+ }
+
+ // Called when the user presses a key, also called for repeating keys held
+ // down
+ @Override
+ public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+ // If the user has held the scroll key down for a while then accelerate
+ // the scroll speed
+ double scrollDist = event.getRepeatCount() >= 5 ? 50.0 : 10.0;
+
+ // Check which key was pressed via hardware/real key code
+ switch (keyCode) {
+ // Tell the system to track these keys for long presses on
+ // onKeyLongPress is fired
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ event.startTracking();
+ return true;
+
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Move left
+ mNativeMapView.moveBy(scrollDist / mScreenDensity, 0.0 / mScreenDensity);
+ return true;
+
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Move right
+ mNativeMapView.moveBy(-scrollDist / mScreenDensity, 0.0 / mScreenDensity);
+ return true;
+
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Move up
+ mNativeMapView.moveBy(0.0 / mScreenDensity, scrollDist / mScreenDensity);
+ return true;
+
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Move down
+ mNativeMapView.moveBy(0.0 / mScreenDensity, -scrollDist / mScreenDensity);
+ return true;
+
+ default:
+ // We are not interested in this key
+ return super.onKeyUp(keyCode, event);
+ }
+ }
+
+ // Called when the user long presses a key that is being tracked
+ @Override
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ // Check which key was pressed via hardware/real key code
+ switch (keyCode) {
+ // Tell the system to track these keys for long presses on
+ // onKeyLongPress is fired
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ // Zoom out
+ zoom(false);
+ return true;
+
+ default:
+ // We are not interested in this key
+ return super.onKeyUp(keyCode, event);
+ }
+ }
+
+ // Called when the user releases a key
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ // Check if the key action was canceled (used for virtual keyboards)
+ if (event.isCanceled()) {
+ return super.onKeyUp(keyCode, event);
+ }
+
+ // Check which key was pressed via hardware/real key code
+ // Note if keyboard does not have physical key (ie primary non-shifted
+ // key) then it will not appear here
+ // Must use the key character map as physical to character is not
+ // fixed/guaranteed
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ // Zoom in
+ zoom(true);
+ return true;
+ }
+
+ // We are not interested in this key
+ return super.onKeyUp(keyCode, event);
+ }
+
+ // Called for trackball events, all motions are relative in device specific
+ // units
+ // TODO: test trackball click and long click
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ // Choose the action
+ switch (event.getActionMasked()) {
+ // The trackball was rotated
+ case MotionEvent.ACTION_MOVE:
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Scroll the map
+ mNativeMapView.moveBy(-10.0 * event.getX() / mScreenDensity, -10.0 * event.getY() / mScreenDensity);
+ return true;
+
+ // Trackball was pushed in so start tracking and tell system we are
+ // interested
+ // We will then get the up action
+ case MotionEvent.ACTION_DOWN:
+ // Set up a delayed callback to check if trackball is still
+ // After waiting the system long press time out
+ if (mCurrentTrackballLongPressTimeOut != null) {
+ mCurrentTrackballLongPressTimeOut.cancel();
+ mCurrentTrackballLongPressTimeOut = null;
+ }
+ mCurrentTrackballLongPressTimeOut = new TrackballLongPressTimeOut();
+ postDelayed(mCurrentTrackballLongPressTimeOut,
+ ViewConfiguration.getLongPressTimeout());
+ return true;
+
+ // Trackball was released
+ case MotionEvent.ACTION_UP:
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ // Only handle if we have not already long pressed
+ if (mCurrentTrackballLongPressTimeOut != null) {
+ // Zoom in
+ zoom(true);
+ }
+ return true;
+
+ // Trackball was cancelled
+ case MotionEvent.ACTION_CANCEL:
+ if (mCurrentTrackballLongPressTimeOut != null) {
+ mCurrentTrackballLongPressTimeOut.cancel();
+ mCurrentTrackballLongPressTimeOut = null;
+ }
+ return true;
+
+ default:
+ // We are not interested in this event
+ return super.onTrackballEvent(event);
+ }
+ }
+
+ // This class implements the trackball long press time out callback
+ private class TrackballLongPressTimeOut implements Runnable {
+
+ // Track if we have been cancelled
+ private boolean cancelled;
+
+ public TrackballLongPressTimeOut() {
+ cancelled = false;
+ }
+
+ // Cancel the timeout
+ public void cancel() {
+ cancelled = true;
+ }
+
+ // Called when long press time out expires
+ @Override
+ public void run() {
+ // Check if the trackball is still pressed
+ if (!cancelled) {
+ // Zoom out
+ zoom(false);
+
+ // Ensure the up action is not run
+ mCurrentTrackballLongPressTimeOut = null;
+ }
+ }
+ }
+
+ // Called for events that don't fit the other handlers
+ // such as mouse scroll events, mouse moves, joystick, trackpad
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ // Mouse events
+ // TODO: SOURCE_TOUCH_NAVIGATION?
+ // TODO: source device resolution?
+ //if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { // this is not available before API 18
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == InputDevice.SOURCE_CLASS_POINTER) {
+ // Choose the action
+ switch (event.getActionMasked()) {
+ // Mouse scrolls
+ case MotionEvent.ACTION_SCROLL:
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Get the vertical scroll amount, one click = 1
+ float scrollDist = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+
+ // Scale the map by the appropriate power of two factor
+ mNativeMapView.scaleBy(Math.pow(2.0, scrollDist), event.getX() / mScreenDensity, event.getY() / mScreenDensity);
+
+ return true;
+
+ default:
+ // We are not interested in this event
+ return super.onGenericMotionEvent(event);
+ }
+ }
+
+ // We are not interested in this event
+ return super.onGenericMotionEvent(event);
+ }
+
+ // Called when the mouse pointer enters or exits the view
+ // or when it fades in or out due to movement
+ @Override
+ public boolean onHoverEvent(@NonNull MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ case MotionEvent.ACTION_HOVER_MOVE:
+ // Show the zoom controls
+ if ((mZoomButtonsController != null) && mZoomEnabled) {
+ mZoomButtonsController.setVisible(true);
+ }
+ return true;
+
+ case MotionEvent.ACTION_HOVER_EXIT:
+ // Hide the zoom controls
+ if (mZoomButtonsController != null) {
+ mZoomButtonsController.setVisible(false);
+ }
+
+ default:
+ // We are not interested in this event
+ return super.onHoverEvent(event);
+ }
+ }
+
+ //
+ // Action events
+ //
+
+ // This class handles connectivity changes
+ public class ConnectivityReceiver extends BroadcastReceiver {
+
+ // Called when an action we are listening to in the manifest has been sent
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.v(TAG, "ConnectivityReceiver.onReceive: action = " + intent.getAction());
+ if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+ onConnectivityChanged(!noConnectivity);
+ }
+ }
+ }
+
+
+ // Called when our Internet connectivity has changed
+ private void onConnectivityChanged(boolean isConnected) {
+ Log.v(TAG, "onConnectivityChanged: " + isConnected);
+ mNativeMapView.setReachability(isConnected);
+ }
+
+ //
+ // Accessibility events
+ //
+
+ //
+ // Map events
+ //
+
+ public interface OnMapChangedListener {
+ void onMapChanged();
+ }
+
+ private OnMapChangedListener mOnMapChangedListener;
+
+ // Adds a listener for onMapChanged
+ public void setOnMapChangedListener(OnMapChangedListener listener) {
+ mOnMapChangedListener = listener;
+ }
+
+ // Called when the map view transformation has changed
+ // Called via JNI from NativeMapView
+ // Need to update anything that relies on map state
+ protected void onMapChanged() {
+ if (mOnMapChangedListener != null) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ mOnMapChangedListener.onMapChanged();
+ }
+ });
+ }
+ }
+
+ public interface OnFpsChangedListener {
+ void onFpsChanged(double fps);
+ }
+
+ private OnFpsChangedListener mOnFpsChangedListener;
+
+ // Adds a listener for onFpsChanged
+ public void setOnFpsChangedListener(OnFpsChangedListener listener) {
+ mOnFpsChangedListener = listener;
+ }
+
+ // Called when debug mode is enabled to update a FPS counter
+ // Called via JNI from NativeMapView
+ protected void onFpsChanged(final double fps) {
+ if (mOnFpsChangedListener != null) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ mOnFpsChangedListener.onFpsChanged(fps);
+ }
+ });
+ }
+ }
+}
diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java
new file mode 100644
index 0000000000..a10a825d5e
--- /dev/null
+++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java
@@ -0,0 +1,542 @@
+package com.mapbox.mapboxgl.lib;
+
+import android.view.Surface;
+
+import java.util.List;
+
+// Class that wraps the native methods for convenience
+class NativeMapView {
+
+ //
+ // Static members
+ //
+
+ // Tag used for logging
+ private static final String TAG = "NativeMapView";
+
+ //
+ // Instance members
+ //
+
+ // Holds the pointer to JNI NativeMapView
+ private long mNativeMapViewPtr = 0;
+
+ // Used for callbacks
+ private MapView mMapView;
+
+ //
+ // Static methods
+ //
+
+ static {
+ System.loadLibrary("mapbox-gl");
+ }
+
+ //
+ // Constructors
+ //
+
+ public NativeMapView(MapView mapView, String cachePath, String dataPath, String apkPath) {
+ mMapView = mapView;
+
+ // Create the NativeMapView
+ mNativeMapViewPtr = nativeCreate(cachePath, dataPath, apkPath);
+ }
+
+ //
+ // Methods
+ //
+
+ public void initializeDisplay() {
+ nativeInitializeDisplay(mNativeMapViewPtr);
+ }
+
+ public void terminateDisplay() {
+ nativeTerminateDisplay(mNativeMapViewPtr);
+ }
+
+ public void initializeContext() {
+ nativeInitializeContext(mNativeMapViewPtr);
+ }
+
+ public void terminateContext() {
+ nativeTerminateContext(mNativeMapViewPtr);
+ }
+
+ public void createSurface(Surface surface) {
+ nativeCreateSurface(mNativeMapViewPtr, surface);
+ }
+
+ public void destroySurface() {
+ nativeDestroySurface(mNativeMapViewPtr);
+ }
+
+ public void start() {
+ nativeStart(mNativeMapViewPtr);
+ }
+
+ public void stop() {
+ nativeStop(mNativeMapViewPtr);
+ }
+
+ public void pause() {
+ nativePause(mNativeMapViewPtr);
+ }
+
+ public void resume() {
+ nativeResume(mNativeMapViewPtr);
+ }
+
+ public void run() {
+ nativeRun(mNativeMapViewPtr);
+ }
+
+ public void rerender() {
+ nativeRerender(mNativeMapViewPtr);
+ }
+
+ public void update() {
+ nativeUpdate(mNativeMapViewPtr);
+ }
+
+ public void cleanup() {
+ nativeCleanup(mNativeMapViewPtr);
+ }
+
+ public void terminate() {
+ nativeTerminate(mNativeMapViewPtr);
+ }
+
+ public boolean needsSwap() {
+ return nativeNeedsSwap(mNativeMapViewPtr);
+ }
+
+ public void swapped() {
+ nativeSwapped(mNativeMapViewPtr);
+ }
+
+ public void resize(int width, int height) {
+ resize(width, height, 1.0f);
+ }
+
+ public void resize(int width, int height, float ratio) {
+ if (width < 0) {
+ throw new IllegalArgumentException("width cannot be negative.");
+ }
+
+ if (height < 0) {
+ throw new IllegalArgumentException("height cannot be negative.");
+ }
+
+ if (width > 65535) {
+ throw new IllegalArgumentException(
+ "width cannot be greater than 65535.");
+ }
+
+ if (height > 65535) {
+ throw new IllegalArgumentException(
+ "height cannot be greater than 65535.");
+ }
+
+ nativeResize(mNativeMapViewPtr, width, height, ratio);
+ }
+
+ public void resize(int width, int height, float ratio, int fbWidth,
+ int fbHeight) {
+ if (width < 0) {
+ throw new IllegalArgumentException("width cannot be negative.");
+ }
+
+ if (height < 0) {
+ throw new IllegalArgumentException("height cannot be negative.");
+ }
+
+ if (fbWidth < 0) {
+ throw new IllegalArgumentException("fbWidth cannot be negative.");
+ }
+
+ if (fbHeight < 0) {
+ throw new IllegalArgumentException("fbHeight cannot be negative.");
+ }
+
+ if (fbWidth > 65535) {
+ throw new IllegalArgumentException(
+ "fbWidth cannot be greater than 65535.");
+ }
+
+ if (fbHeight > 65535) {
+ throw new IllegalArgumentException(
+ "fbHeight cannot be greater than 65535.");
+ }
+ nativeResize(mNativeMapViewPtr, width, height, ratio, fbWidth, fbHeight);
+ }
+
+ public void setAppliedClasses(List<String> classes) {
+ nativeSetAppliedClasses(mNativeMapViewPtr, classes);
+ }
+
+ public List<String> getAppliedClasses() {
+ return nativeGetAppliedClasses(mNativeMapViewPtr);
+ }
+
+ public void setDefaultTransitionDuration() {
+ setDefaultTransitionDuration(0);
+ }
+
+ public long getDefaultTransitionDuration() {
+ return nativeGetDefaultTransitionDuration(mNativeMapViewPtr);
+ }
+
+ public void setDefaultTransitionDuration(long milliseconds) {
+ if (milliseconds < 0) {
+ throw new IllegalArgumentException(
+ "durationMilliseconds cannot be negative.");
+ }
+
+ nativeSetDefaultTransitionDuration(mNativeMapViewPtr,
+ milliseconds);
+ }
+
+ public void setStyleUrl(String url) {
+ nativeSetStyleUrl(mNativeMapViewPtr, url);
+ }
+
+ public void setStyleJson(String newStyleJson) {
+ setStyleJson(newStyleJson, "");
+ }
+
+ public void setStyleJson(String newStyleJson, String base) {
+ nativeSetStyleJson(mNativeMapViewPtr, newStyleJson, base);
+ }
+
+ public String getStyleJson() {
+ return nativeGetStyleJson(mNativeMapViewPtr);
+ }
+
+ public void setAccessToken(String accessToken) {
+ nativeSetAccessToken(mNativeMapViewPtr, accessToken);
+ }
+
+ public String getAccessToken() {
+ return nativeGetAccessToken(mNativeMapViewPtr);
+ }
+
+ public void cancelTransitions() {
+ nativeCancelTransitions(mNativeMapViewPtr);
+ }
+
+ public void moveBy(double dx, double dy) {
+ moveBy(dx, dy, 0.0);
+ }
+
+ public void moveBy(double dx, double dy, double duration) {
+ nativeMoveBy(mNativeMapViewPtr, dx, dy, duration);
+ }
+
+ public void setLonLat(LonLat lonLat) {
+ setLonLat(lonLat, 0.0);
+ }
+
+ public void setLonLat(LonLat lonLat, double duration) {
+ nativeSetLonLat(mNativeMapViewPtr, lonLat, duration);
+ }
+
+ public LonLat getLonLat() {
+ return nativeGetLonLat(mNativeMapViewPtr);
+ }
+
+ public void startPanning() {
+ nativeStartPanning(mNativeMapViewPtr);
+ }
+
+ public void stopPanning() {
+ nativeStopPanning(mNativeMapViewPtr);
+ }
+
+ public void resetPosition() {
+ nativeResetPosition(mNativeMapViewPtr);
+ }
+
+ public void scaleBy(double ds) {
+ scaleBy(ds, -1.0, -1.0);
+ }
+
+ public void scaleBy(double ds, double cx, double cy) {
+ scaleBy(ds, cx, cy, 0.0);
+ }
+
+ public void scaleBy(double ds, double cx, double cy, double duration) {
+ nativeScaleBy(mNativeMapViewPtr, ds, cx, cy, duration);
+ }
+
+ public void setScale(double scale) {
+ setScale(scale, -1.0, -1.0);
+ }
+
+ public void setScale(double scale, double cx, double cy) {
+ setScale(scale, cx, cy, 0.0);
+ }
+
+ public void setScale(double scale, double cx, double cy, double duration) {
+ nativeSetScale(mNativeMapViewPtr, scale, cx, cy, duration);
+ }
+
+ public double getScale() {
+ return nativeGetScale(mNativeMapViewPtr);
+ }
+
+ public void setZoom(double zoom) {
+ setZoom(zoom, 0.0);
+ }
+
+ public void setZoom(double zoom, double duration) {
+ nativeSetZoom(mNativeMapViewPtr, zoom, duration);
+ }
+
+ public double getZoom() {
+ return nativeGetZoom(mNativeMapViewPtr);
+ }
+
+ public void setLonLatZoom(LonLatZoom lonLatZoom) {
+ setLonLatZoom(lonLatZoom, 0.0);
+ }
+
+ public void setLonLatZoom(LonLatZoom lonLatZoom, double duration) {
+ nativeSetLonLatZoom(mNativeMapViewPtr, lonLatZoom, duration);
+ }
+
+ public LonLatZoom getLonLatZoom() {
+ return nativeGetLonLatZoom(mNativeMapViewPtr);
+ }
+
+ public void resetZoom() {
+ nativeResetZoom(mNativeMapViewPtr);
+ }
+
+ public void startScaling() {
+ nativeStartScaling(mNativeMapViewPtr);
+ }
+
+ public void stopScaling() {
+ nativeStopScaling(mNativeMapViewPtr);
+ }
+
+ public double getMinZoom() {
+ return nativeGetMinZoom(mNativeMapViewPtr);
+ }
+
+ public double getMaxZoom() {
+ return nativeGetMaxZoom(mNativeMapViewPtr);
+ }
+
+ public void rotateBy(double sx, double sy, double ex, double ey) {
+ rotateBy(sx, sy, ex, ey, 0.0);
+ }
+
+ public void rotateBy(double sx, double sy, double ex, double ey,
+ double duration) {
+ nativeRotateBy(mNativeMapViewPtr, sx, sy, ex, ey, duration);
+ }
+
+ public void setBearing(double degrees) {
+ setBearing(degrees, 0.0);
+ }
+
+ public void setBearing(double degrees, double duration) {
+ nativeSetBearing(mNativeMapViewPtr, degrees, duration);
+ }
+
+ public void setBearing(double degrees, double cx, double cy) {
+ nativeSetBearing(mNativeMapViewPtr, degrees, cx, cy);
+ }
+
+ public double getBearing() {
+ return nativeGetBearing(mNativeMapViewPtr);
+ }
+
+ public void resetNorth() {
+ nativeResetNorth(mNativeMapViewPtr);
+ }
+
+ public void startRotating() {
+ nativeStartRotating(mNativeMapViewPtr);
+ }
+
+ public void stopRotating() {
+ nativeStopRotating(mNativeMapViewPtr);
+ }
+
+ public void setDebug(boolean debug) {
+ nativeSetDebug(mNativeMapViewPtr, debug);
+ }
+
+ public void toggleDebug() {
+ nativeToggleDebug(mNativeMapViewPtr);
+ }
+
+ public boolean getDebug() {
+ return nativeGetDebug(mNativeMapViewPtr);
+ }
+
+ public void setReachability(boolean status) {
+ nativeSetReachability(mNativeMapViewPtr, status);
+ }
+
+ //
+ // Callbacks
+ //
+
+ protected void onMapChanged() {
+ mMapView.onMapChanged();
+ }
+
+ protected void onFpsChanged(double fps) {
+ mMapView.onFpsChanged(fps);
+ }
+
+ //
+ // JNI methods
+ //
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeDestroy(mNativeMapViewPtr);
+ mNativeMapViewPtr = 0;
+ super.finalize();
+ }
+
+ private native long nativeCreate(String cachePath, String dataPath, String apkPath);
+
+ private native void nativeDestroy(long nativeMapViewPtr);
+
+ private native void nativeInitializeDisplay(long nativeMapViewPtr);
+
+ private native void nativeTerminateDisplay(long nativeMapViewPtr);
+
+ private native void nativeInitializeContext(long nativeMapViewPtr);
+
+ private native void nativeTerminateContext(long nativeMapViewPtr);
+
+ private native void nativeCreateSurface(long nativeMapViewPtr,
+ Surface surface);
+
+ private native void nativeDestroySurface(long nativeMapViewPtr);
+
+ private native void nativeStart(long nativeMapViewPtr);
+
+ private native void nativeStop(long nativeMapViewPtr);
+
+ private native void nativePause(long nativeMapViewPtr);
+
+ private native void nativeResume(long nativeMapViewPtr);
+
+ private native void nativeRun(long nativeMapViewPtr);
+
+ private native void nativeRerender(long nativeMapViewPtr);
+
+ private native void nativeUpdate(long nativeMapViewPtr);
+
+ private native void nativeCleanup(long nativeMapViewPtr);
+
+ private native void nativeTerminate(long nativeMapViewPtr);
+
+ private native boolean nativeNeedsSwap(long nativeMapViewPtr);
+
+ private native void nativeSwapped(long nativeMapViewPtr);
+
+ private native void nativeResize(long nativeMapViewPtr, int width,
+ int height, float ratio);
+
+ private native void nativeResize(long nativeMapViewPtr, int width,
+ int height, float ratio, int fbWidth, int fbHeight);
+
+ private native void nativeSetAppliedClasses(long nativeMapViewPtr,
+ List<String> classes);
+
+ private native List<String> nativeGetAppliedClasses(long nativeMapViewPtr);
+
+ private native void nativeSetDefaultTransitionDuration(
+ long nativeMapViewPtr, long milliseconds);
+
+ private native long nativeGetDefaultTransitionDuration(long nativeMapViewPtr);
+
+ private native void nativeSetStyleUrl(long nativeMapViewPtr, String url);
+
+ private native void nativeSetStyleJson(long nativeMapViewPtr,
+ String newStyleJson, String base);
+
+ private native String nativeGetStyleJson(long nativeMapViewPtr);
+
+ private native void nativeSetAccessToken(long nativeMapViewPtr, String accessToken);
+
+ private native String nativeGetAccessToken(long nativeMapViewPtr);
+
+ private native void nativeCancelTransitions(long nativeMapViewPtr);
+
+ private native void nativeMoveBy(long nativeMapViewPtr, double dx,
+ double dy, double duration);
+
+ private native void nativeSetLonLat(long nativeMapViewPtr, LonLat lonLat,
+ double duration);
+
+ private native LonLat nativeGetLonLat(long nativeMapViewPtr);
+
+ private native void nativeStartPanning(long nativeMapViewPtr);
+
+ private native void nativeStopPanning(long nativeMapViewPtr);
+
+ private native void nativeResetPosition(long nativeMapViewPtr);
+
+ private native void nativeScaleBy(long nativeMapViewPtr, double ds,
+ double cx, double cy, double duration);
+
+ private native void nativeSetScale(long nativeMapViewPtr, double scale,
+ double cx, double cy, double duration);
+
+ private native double nativeGetScale(long nativeMapViewPtr);
+
+ private native void nativeSetZoom(long nativeMapViewPtr, double zoom,
+ double duration);
+
+ private native double nativeGetZoom(long nativeMapViewPtr);
+
+ private native void nativeSetLonLatZoom(long nativeMapViewPtr,
+ LonLatZoom lonLatZoom, double duration);
+
+ private native LonLatZoom nativeGetLonLatZoom(long nativeMapViewPtr);
+
+ private native void nativeResetZoom(long nativeMapViewPtr);
+
+ private native void nativeStartScaling(long nativeMapViewPtr);
+
+ private native void nativeStopScaling(long nativeMapViewPtr);
+
+ private native double nativeGetMinZoom(long nativeMapViewPtr);
+
+ private native double nativeGetMaxZoom(long nativeMapViewPtr);
+
+ private native void nativeRotateBy(long nativeMapViewPtr, double sx,
+ double sy, double ex, double ey, double duration);
+
+ private native void nativeSetBearing(long nativeMapViewPtr, double degrees,
+ double duration);
+
+ private native void nativeSetBearing(long nativeMapViewPtr, double degrees,
+ double cx, double cy);
+
+ private native double nativeGetBearing(long nativeMapViewPtr);
+
+ private native void nativeResetNorth(long nativeMapViewPtr);
+
+ private native void nativeStartRotating(long nativeMapViewPtr);
+
+ private native void nativeStopRotating(long nativeMapViewPtr);
+
+ private native void nativeSetDebug(long nativeMapViewPtr, boolean debug);
+
+ private native void nativeToggleDebug(long nativeMapViewPtr);
+
+ private native boolean nativeGetDebug(long nativeMapViewPtr);
+
+ private native void nativeSetReachability(long nativeMapViewPtr, boolean status);
+}
diff --git a/android/java/lib/src/main/res/values/attrs.xml b/android/java/lib/src/main/res/values/attrs.xml
new file mode 100644
index 0000000000..770b703175
--- /dev/null
+++ b/android/java/lib/src/main/res/values/attrs.xml
@@ -0,0 +1,23 @@
+<resources>
+ <attr name="centerLongitude" format="float" />
+ <attr name="centerLatitude" format="float" />
+ <attr name="zoomLevel" format="float" />
+ <attr name="direction" format="float" />
+ <attr name="zoomEnabled" format="boolean" />
+ <attr name="scrollEnabled" format="boolean" />
+ <attr name="rotateEnabled" format="boolean" />
+ <attr name="debugActive" format="boolean" />
+ <attr name="styleUrl" format="string" />
+
+ <declare-styleable name="MapView">
+ <attr name="centerLongitude" />
+ <attr name="centerLatitude" />
+ <attr name="zoomLevel" />
+ <attr name="direction" />
+ <attr name="zoomEnabled" />
+ <attr name="scrollEnabled" />
+ <attr name="rotateEnabled" />
+ <attr name="debugActive" />
+ <attr name="styleUrl" />
+ </declare-styleable>
+</resources>
diff --git a/android/java/settings.gradle b/android/java/settings.gradle
new file mode 100644
index 0000000000..0959cbb33e
--- /dev/null
+++ b/android/java/settings.gradle
@@ -0,0 +1,2 @@
+include ':app'
+include ':lib'
diff --git a/android/mapboxgl-app.gyp b/android/mapboxgl-app.gyp
new file mode 100644
index 0000000000..938546636c
--- /dev/null
+++ b/android/mapboxgl-app.gyp
@@ -0,0 +1,66 @@
+{
+ 'includes': [
+ '../gyp/common.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'androidapp',
+ 'product_name': 'mapbox-gl',
+ 'type': 'shared_library',
+ 'sources': [
+ './cpp/native_map_view.cpp',
+ './cpp/jni.cpp',
+ './cpp/coffeecatch.c',
+ './cpp/coffeejni.c',
+ ],
+ 'cflags_cc': [
+ '-I<(boost_root)/include',
+ ],
+ 'libraries': [
+ '<@(openssl_static_libs)',
+ '<@(curl_static_libs)',
+ '<@(png_static_libs)',
+ '<@(jpeg_static_libs)',
+ '<@(sqlite3_static_libs)',
+ '<@(uv_static_libs)',
+ '<@(nu_static_libs)',
+ '<@(zip_static_libs)',
+ ],
+ 'variables': {
+ 'ldflags': [
+ '-llog',
+ '-landroid',
+ '-lEGL',
+ '-lGLESv2',
+ '-lstdc++',
+ '-latomic',
+ '<@(png_ldflags)',
+ '<@(jpeg_ldflags)',
+ '<@(sqlite3_ldflags)',
+ '<@(openssl_ldflags)',
+ '<@(curl_ldflags)',
+ '<@(zlib_ldflags)',
+ '<@(zip_ldflags)',
+ ],
+ },
+ 'conditions': [
+ ['OS == "mac"', {
+ 'xcode_settings': {
+ 'OTHER_LDFLAGS': [ '<@(ldflags)' ],
+ }
+ }, {
+ 'libraries': [ '<@(ldflags)' ],
+ }]
+ ],
+ 'dependencies': [
+ '../mapboxgl.gyp:mbgl-standalone',
+ '../mapboxgl.gyp:mbgl-android',
+ '../mapboxgl.gyp:copy_certificate_bundle',
+ ],
+ 'copies': [{
+ 'files': [ '../styles' ],
+ 'destination': '<(PRODUCT_DIR)'
+ }],
+ },
+ ],
+}
diff --git a/android/test/.gitignore b/android/test/.gitignore
new file mode 100644
index 0000000000..68c12ecc03
--- /dev/null
+++ b/android/test/.gitignore
@@ -0,0 +1 @@
+features.zip
diff --git a/android/test/features/load-app.feature b/android/test/features/load-app.feature
new file mode 100644
index 0000000000..e6bd1158a4
--- /dev/null
+++ b/android/test/features/load-app.feature
@@ -0,0 +1,5 @@
+Feature: Testapp V.1.2
+
+Scenario: 1) Start app and load map
+ Given I await for 5 seconds
+
diff --git a/android/test/features/step_definitions/calabash_steps.rb b/android/test/features/step_definitions/calabash_steps.rb
new file mode 100755
index 0000000000..6d80ff0c97
--- /dev/null
+++ b/android/test/features/step_definitions/calabash_steps.rb
@@ -0,0 +1 @@
+require 'calabash-android/calabash_steps'
diff --git a/android/test/features/step_definitions/my_steps.rb b/android/test/features/step_definitions/my_steps.rb
new file mode 100644
index 0000000000..3239322ddc
--- /dev/null
+++ b/android/test/features/step_definitions/my_steps.rb
@@ -0,0 +1,6 @@
+
+Then /^I await for ([\d\.]+) second(?:s)?$/ do |num_seconds|
+num_seconds = num_seconds.to_f
+sleep num_seconds
+
+end
diff --git a/android/test/features/support/app_installation_hooks.rb b/android/test/features/support/app_installation_hooks.rb
new file mode 100755
index 0000000000..2e0a203bc5
--- /dev/null
+++ b/android/test/features/support/app_installation_hooks.rb
@@ -0,0 +1,36 @@
+require 'calabash-android/management/app_installation'
+
+AfterConfiguration do |config|
+ FeatureNameMemory.feature_name = nil
+end
+
+Before do |scenario|
+ @scenario_is_outline = (scenario.class == Cucumber::Ast::OutlineTable::ExampleRow)
+ if @scenario_is_outline
+ scenario = scenario.scenario_outline
+ end
+
+ feature_name = scenario.feature.title
+ if FeatureNameMemory.feature_name != feature_name \
+ or ENV["RESET_BETWEEN_SCENARIOS"] == "1"
+ if ENV["RESET_BETWEEN_SCENARIOS"] == "1"
+ log "New scenario - reinstalling apps"
+ else
+ log "First scenario in feature - reinstalling apps"
+ end
+
+ uninstall_apps
+ install_app(ENV["TEST_APP_PATH"])
+ install_app(ENV["APP_PATH"])
+ FeatureNameMemory.feature_name = feature_name
+ FeatureNameMemory.invocation = 1
+ else
+ FeatureNameMemory.invocation += 1
+ end
+end
+
+FeatureNameMemory = Class.new
+class << FeatureNameMemory
+ @feature_name = nil
+ attr_accessor :feature_name, :invocation
+end
diff --git a/android/test/features/support/app_life_cycle_hooks.rb b/android/test/features/support/app_life_cycle_hooks.rb
new file mode 100755
index 0000000000..63d4678d9a
--- /dev/null
+++ b/android/test/features/support/app_life_cycle_hooks.rb
@@ -0,0 +1,13 @@
+require 'calabash-android/management/adb'
+require 'calabash-android/operations'
+
+Before do |scenario|
+ start_test_server_in_background
+end
+
+After do |scenario|
+ if scenario.failed?
+ screenshot_embed
+ end
+ shutdown_test_server
+end
diff --git a/android/test/features/support/env.rb b/android/test/features/support/env.rb
new file mode 100755
index 0000000000..bb2505d458
--- /dev/null
+++ b/android/test/features/support/env.rb
@@ -0,0 +1 @@
+require 'calabash-android/cucumber'
diff --git a/android/test/features/support/hooks.rb b/android/test/features/support/hooks.rb
new file mode 100755
index 0000000000..e69de29bb2
--- /dev/null
+++ b/android/test/features/support/hooks.rb
diff --git a/android/test/upload_testmunk.sh b/android/test/upload_testmunk.sh
new file mode 100755
index 0000000000..15c6530d83
--- /dev/null
+++ b/android/test/upload_testmunk.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+
+set -e
+set -o pipefail
+
+EMAIL="leith@mapbox.com"
+APP_NAME="Mapbox"
+APK_PATH=$1
+
+echo "submitting testrun to testmunk"
+
+echo "uploading features..."
+
+zip -r features.zip features/
+RESPONSE=$(curl -f -H "Accept: application/vnd.testmunk.v1+json" -F "file=@features.zip" "https://${TESTMUNK_KEY}@api.testmunk.com/apps/${APP_NAME}/testcases")
+
+echo "uploading apk..."
+
+cd `dirname ${APK_PATH}`
+RESPONSE=$(curl -f -H "Accept: application/vnd.testmunk.v1+json" -F "file=@`basename ${APK_PATH}`" -F "email=${EMAIL}" -F "autoStart=true" -F "public=true" "https://${TESTMUNK_KEY}@api.testmunk.com/apps/${APP_NAME}/testruns")
+
+TESTRUN_ID=$(echo "${RESPONSE}" | jq -r '.id')
+TESTRUN_NAME=$(echo "${RESPONSE}" | jq -r '.name')
+
+echo "successully uploaded to Testmunk as '${TESTRUN_NAME}'"
+echo "public link to test results: https://www.testmunk.com/testrun/${TESTRUN_ID}"
+cd -
diff --git a/configure b/configure
index 8c74ec1892..bacec88c0c 100755
--- a/configure
+++ b/configure
@@ -24,6 +24,18 @@ case ${MASON_PLATFORM} in
ZLIB_VERSION=system
BOOST_VERSION=system
;;
+ 'android')
+ SQLITE_VERSION=3.8.6
+ LIBPNG_VERSION=1.6.13
+ LIBJPEG_VERSION=v8d
+ OPENSSL_VERSION=1.0.1i
+ LIBCURL_VERSION=7.38.0
+ LIBUV_VERSION=0.11.29
+ ZLIB_VERSION=system
+ BOOST_VERSION=1.57.0
+ NUNICODE_VERSION=1.5-dev
+ LIBZIP_VERSION=0.11.2
+ ;;
*)
GLFW_VERSION=e1ae9af5
SQLITE_VERSION=system
@@ -69,6 +81,13 @@ if [ ! -z ${BOOST_VERSION} ]; then
CONFIG+=" 'boost_root': '$(mason prefix boost ${BOOST_VERSION})',"$LN
fi
+if [ ! -z ${OPENSSL_VERSION} ]; then
+ mason install openssl ${OPENSSL_VERSION}
+ CONFIG+=" 'openssl_static_libs': $(quote_flags $(mason static_libs openssl ${OPENSSL_VERSION})),"$LN
+ CONFIG+=" 'openssl_cflags': $(quote_flags $(mason cflags openssl ${OPENSSL_VERSION})),"$LN
+ CONFIG+=" 'openssl_ldflags': $(quote_flags $(mason ldflags openssl ${OPENSSL_VERSION})),"$LN
+fi
+
if [ ! -z ${LIBCURL_VERSION} ]; then
mason install libcurl ${LIBCURL_VERSION}
CONFIG+=" 'curl_static_libs': $(quote_flags $(mason static_libs libcurl ${LIBCURL_VERSION})),"$LN
@@ -127,6 +146,12 @@ if [ ! -z ${NUNICODE_VERSION} ]; then
CONFIG+=" 'nu_ldflags': $(quote_flags $(mason ldflags nunicode ${NUNICODE_VERSION})),"$LN
fi
+if [ ! -z ${LIBZIP_VERSION} ]; then
+ mason install libzip ${LIBZIP_VERSION}
+ CONFIG+=" 'zip_static_libs': $(quote_flags $(mason static_libs libzip ${LIBZIP_VERSION})),"$LN
+ CONFIG+=" 'zip_cflags': $(quote_flags $(mason cflags libzip ${LIBZIP_VERSION})),"$LN
+ CONFIG+=" 'zip_ldflags': $(quote_flags $(mason ldflags libzip ${LIBZIP_VERSION})),"$LN
+fi
CONFIG+=" }
}
diff --git a/gyp/common.gypi b/gyp/common.gypi
index 6d5fb7690d..d219ba2f99 100644
--- a/gyp/common.gypi
+++ b/gyp/common.gypi
@@ -32,6 +32,7 @@
'cflags_cc': [
'-Wno-unknown-pragmas', # We are using '#pragma mark', but it is only available on Darwin.
'-Wno-literal-suffix', # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61653
+ '-Wno-unknown-warning-option',
'-Wno-error=unused-parameter',
],
}],
diff --git a/gyp/mbgl-android.gypi b/gyp/mbgl-android.gypi
new file mode 100644
index 0000000000..ff537792b4
--- /dev/null
+++ b/gyp/mbgl-android.gypi
@@ -0,0 +1,78 @@
+{
+ 'targets': [
+ { 'target_name': 'mbgl-android',
+ 'product_name': 'mbgl-android',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'hard_dependency': 1,
+ 'variables': {
+ 'cflags_cc': [
+ '<@(png_cflags)',
+ '<@(jpeg_cflags)',
+ '<@(uv_cflags)',
+ '<@(curl_cflags)',
+ '<@(nu_cflags)',
+ '<@(zip_cflags)',
+ '<@(openssl_cflags)',
+ '-I<(boost_root)/include',
+ ],
+ 'cflags': [
+ '<@(uv_cflags)',
+ '<@(nu_cflags)',
+ ],
+ 'ldflags': [
+ '<@(png_ldflags)',
+ '<@(jpeg_ldflags)',
+ '<@(uv_ldflags)',
+ '<@(curl_ldflags)',
+ '<@(nu_ldflags)',
+ '<@(zip_ldflags)',
+ ],
+ },
+ 'sources': [
+ '../platform/android/cache_database_data.cpp',
+ '../platform/android/log_android.cpp',
+ '../platform/android/asset_request_baton_libzip.cpp',
+ '../platform/default/string_stdlib.cpp',
+ '../platform/default/http_request_baton_curl.cpp',
+ '../platform/default/image.cpp',
+ '../platform/default/image_reader.cpp',
+ '../platform/default/png_reader.cpp',
+ '../platform/default/jpeg_reader.cpp',
+ ],
+ 'include_dirs': [
+ '../include',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '<@(png_static_libs)',
+ ],
+ },
+ 'conditions': [
+ ['OS == "mac"', {
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
+ 'OTHER_CFLAGS': [ '<@(cflags)' ],
+ }
+ }, {
+ 'cflags_cc': [ '<@(cflags_cc)' ],
+ 'cflags': [ '<@(cflags)' ],
+ }]
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '../include',
+ ],
+ 'conditions': [
+ ['OS == "mac"', {
+ 'xcode_settings': {
+ 'OTHER_LDFLAGS': [ '<@(ldflags)' ],
+ }
+ }, {
+ 'ldflags': [ '<@(ldflags)' ],
+ }]
+ ],
+ },
+ },
+ ],
+}
diff --git a/gyp/mbgl-ios.gypi b/gyp/mbgl-ios.gypi
index ec31869ad9..a79737b0cd 100644
--- a/gyp/mbgl-ios.gypi
+++ b/gyp/mbgl-ios.gypi
@@ -37,6 +37,7 @@
'../platform/darwin/string_nsstring.mm',
'../platform/darwin/http_request_baton_cocoa.mm',
'../platform/darwin/image.mm',
+ '../platform/default/asset_request_baton_noop.cpp',
],
'include_dirs': [
'../include',
diff --git a/gyp/mbgl-linux.gypi b/gyp/mbgl-linux.gypi
index 7af08242ee..c42a49d9d1 100644
--- a/gyp/mbgl-linux.gypi
+++ b/gyp/mbgl-linux.gypi
@@ -36,6 +36,7 @@
'../platform/default/image_reader.cpp',
'../platform/default/png_reader.cpp',
'../platform/default/jpeg_reader.cpp',
+ '../platform/default/asset_request_baton_noop.cpp',
],
'include_dirs': [
'../include',
diff --git a/gyp/mbgl-osx.gypi b/gyp/mbgl-osx.gypi
index 09c96807aa..542f6cb9e3 100644
--- a/gyp/mbgl-osx.gypi
+++ b/gyp/mbgl-osx.gypi
@@ -12,6 +12,7 @@
'../platform/darwin/string_nsstring.mm',
'../platform/darwin/http_request_baton_cocoa.mm',
'../platform/darwin/image.mm',
+ '../platform/default/asset_request_baton_noop.cpp',
],
'include_dirs': [
'../include',
diff --git a/gyp/mbgl-platform.gypi b/gyp/mbgl-platform.gypi
index 74ac9aecc6..08fa5f821c 100644
--- a/gyp/mbgl-platform.gypi
+++ b/gyp/mbgl-platform.gypi
@@ -12,5 +12,9 @@
'includes': ['mbgl-linux.gypi'],
'variables': { 'platform_library': 'mbgl-linux' },
}],
+ ['platform == "android"', {
+ 'includes': ['mbgl-android.gypi'],
+ 'variables': { 'platform_library': 'mbgl-android' },
+ }],
],
}
diff --git a/include/coffeecatch/coffeecatch.h b/include/coffeecatch/coffeecatch.h
new file mode 100644
index 0000000000..3aa00ddd40
--- /dev/null
+++ b/include/coffeecatch/coffeecatch.h
@@ -0,0 +1,226 @@
+/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.
+ * (especially for Android/Dalvik)
+ *
+ * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
+ * All rights reserved.
+ * See the "License" section below for the licensing terms.
+ *
+ * Description:
+ *
+ * Allows to "gracefully" recover from a signal (segv, sibus...) as if it was
+ * a Java exception. It will not gracefully recover from allocator/mutexes
+ * corruption etc., however, but at least "most" gentle crashes (null pointer
+ * dereferencing, integer division, stack overflow etc.) should be handled
+ * without too much troubles.
+ *
+ * The handler is thread-safe, but client must have exclusive control on the
+ * signal handlers (ie. the library is installing its own signal handlers on
+ * top of the existing ones).
+ *
+ * You must build all your libraries with `-funwind-tables', to get proper
+ * unwinding information on all binaries. On ARM, you may also use the
+ * `--no-merge-exidx-entries` linker switch, to solve certain issues with
+ * unwinding (the switch is possibly not needed anymore).
+ * On Android, this can be achieved by using this line in the Android.mk file
+ * in each library block:
+ * LOCAL_CFLAGS := -funwind-tables -Wl,--no-merge-exidx-entries
+ *
+ * Example:
+ *
+ * COFFEE_TRY() {
+ * call_some_native_function()
+ * } COFFEE_CATCH() {
+ * const char*const message = coffeecatch_get_message();
+ * jclass cls = (*env)->FindClass(env, "java/lang/RuntimeException");
+ * (*env)->ThrowNew(env, cls, strdup(message));
+ * } COFFEE_END();
+ *
+ * Implementation notes:
+ *
+ * Currently the library is installing both alternate stack and signal
+ * handlers for known signals (SIGABRT, SIGILL, SIGTRAP, SIGBUS, SIGFPE,
+ * SIGSEGV, SIGSTKFLT), and is using sigsetjmp()/siglongjmp() to return to
+ * "userland" (compared to signal handler context). As a security, an alarm
+ * is started as soon as a fatal signal is detected (ie. not something the
+ * JVM will handle) to kill the process after a grace period. Be sure your
+ * program will exit quickly after the error is caught, or call alarm(0)
+ * to cancel the pending time-bomb.
+ * The signal handlers had to be written with caution, because the virtual
+ * machine might be using signals (including SEGV) to handle JIT compiler,
+ * and some clever optimizations (such as NullPointerException handling)
+ * We are using several signal-unsafe functions, namely:
+ * - siglongjmp() to return to userland
+ * - pthread_getspecific() to get thread-specific setup
+ *
+ * License:
+ *
+ * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. 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 OWNER 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.
+ */
+
+#ifndef COFFEECATCH_H
+#define COFFEECATCH_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Setup crash handler to enter in a protected section. If a recognized signal
+ * is received in this section, the execution will be diverted to the
+ * COFFEE_CATCH() block.
+ *
+ * Note: you MUST use the following pattern when using this macro:
+ * COFFEE_TRY() {
+ * .. protected section without exit point
+ * } COFFEE_CATCH() {
+ * .. handler section without exit point
+ * } COFFEE_END();
+ *
+ * You can not exit the protected section block, or the handler section block,
+ * using statements such as "return", because the cleanup code would not be
+ * executed.
+ *
+ * It is advised to enclose this complete try/catch/end block in a dedicated
+ * function declared extern or __attribute__ ((noinline)).
+ *
+ * Example:
+ *
+ * void my_native_function(JNIEnv* env, jobject object, jint *retcode) {
+ * COFFEE_TRY() {
+ * *retcode = call_dangerous_function(env, object);
+ * } COFFEE_CATCH() {
+ * const char*const message = coffeecatch_get_message();
+ * jclass cls = (*env)->FindClass(env, "java/lang/RuntimeException");
+ * (*env)->ThrowNew(env, cls, strdup(message));
+ * *retcode = -1;
+ * } COFFEE_END();
+ * }
+ *
+ * In addition, the following restrictions MUST be followed:
+ * - the function must be declared extern, or with the special attribute
+ * __attribute__ ((noinline)).
+ * - you must not use local variables before the complete try/catch/end block,
+ * or define them as "volatile".
+ * - your function should not ignore the crash silently, as the library will
+ * ensure the process is killed after a grace period (typically 30s) to
+ * prevent any deadlock that may occur if the crash was caught inside a
+ * non-signal-safe function, for example (such as malloc()).
+ *
+COFFEE_TRY()
+ **/
+
+/**
+ * Declare the signal handler block. This block will be executed if a signal
+ * was received, and recognized, in the previous COFFEE_TRY() {} section.
+ * You may call audit functions in this block, such as coffeecatch_get_signal()
+ * or coffeecatch_get_message().
+ *
+COFFEE_CATCH()
+ **/
+
+/**
+ * Declare the end of the COFFEE_TRY()/COFFEE_CATCH() section.
+ * Diagnostic functions must not be called beyond this point.
+ *
+COFFEE_END()
+ **/
+
+/**
+ * Get the signal associated with the crash.
+ * This function can only be called inside a COFFEE_CATCH() block.
+ */
+extern int coffeecatch_get_signal(void);
+
+/**
+ * Get the full error message associated with the crash.
+ * This function can only be called inside a COFFEE_CATCH() block, and the
+ * returned pointer is only valid within this block. (you may want to copy
+ * the string in a static buffer, or use strdup())
+ */
+const char* coffeecatch_get_message(void);
+
+/**
+ * Raise an abort() signal in the current thread. If the current code section
+ * is protected, the 'exp', 'file' and 'line' information are stored for
+ * further audit.
+ */
+extern void coffeecatch_abort(const char* exp, const char* file, int line);
+
+/**
+ * Assertion check. If the expression is false, an abort() signal is raised
+ * using coffeecatch_abort().
+ */
+#define coffeecatch_assert(EXP) (void)( (EXP) || (coffeecatch_abort(#EXP, __FILE__, __LINE__), 0) )
+
+/**
+ * Get the backtrace size, or 0 upon error.
+ * This function can only be called inside a COFFEE_CATCH() block.
+ */
+extern size_t coffeecatch_get_backtrace_size(void);
+
+/**
+ * Get the backtrace pointer, or 0 upon error.
+ * This function can only be called inside a COFFEE_CATCH() block.
+ */
+extern uintptr_t coffeecatch_get_backtrace(ssize_t index);
+
+/**
+ * Enumerate the backtrace with information.
+ * This function can only be called inside a COFFEE_CATCH() block.
+ */
+extern void coffeecatch_get_backtrace_info(void (*fun)(void *arg,
+ const char *module,
+ uintptr_t addr,
+ const char *function,
+ uintptr_t offset), void *arg);
+
+/**
+ * Cancel any pending alarm() triggered after a signal was caught.
+ * Calling this function is dangerous, because it exposes the process to
+ * a possible deadlock if the signal was caught due to internal low-level
+ * library error (mutex being in a locked state, for example).
+ */
+extern int coffeecatch_cancel_pending_alarm(void);
+
+/** Internal functions & definitions, not to be used directly. **/
+#include <setjmp.h>
+extern int coffeecatch_setup(void);
+extern sigjmp_buf* coffeecatch_get_ctx(void);
+extern void coffeecatch_cleanup(void);
+#define COFFEE_TRY() \
+ if (coffeecatch_setup() == 0 \
+ && sigsetjmp(*coffeecatch_get_ctx(), 1) == 0)
+#define COFFEE_CATCH() else
+#define COFFEE_END() coffeecatch_cleanup()
+/** End of internal functions & definitions. **/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/include/coffeecatch/coffeejni.h b/include/coffeecatch/coffeejni.h
new file mode 100644
index 0000000000..3d7fe1b289
--- /dev/null
+++ b/include/coffeecatch/coffeejni.h
@@ -0,0 +1,133 @@
+/* CoffeeCatch, a tiny native signal handler/catcher for JNI code.
+ * (especially for Android/Dalvik)
+ *
+ * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
+ * All rights reserved.
+ * See the "License" section below for the licensing terms.
+ *
+ * Description:
+ *
+ * Allows to "gracefully" recover from a signal (segv, sibus...) as if it was
+ * a Java exception. It will not gracefully recover from allocator/mutexes
+ * corruption etc., however, but at least "most" gentle crashes (null pointer
+ * dereferencing, integer division, stack overflow etc.) should be handled
+ * without too much troubles.
+ *
+ * The handler is thread-safe, but client must have exclusive control on the
+ * signal handlers (ie. the library is installing its own signal handlers on
+ * top of the existing ones).
+ *
+ * You must build all your libraries with `-funwind-tables', to get proper
+ * unwinding information on all binaries. On ARM, you may also use the
+ * `--no-merge-exidx-entries` linker switch, to solve certain issues with
+ * unwinding (the switch is possibly not needed anymore).
+ * On Android, this can be achieved by using this line in the Android.mk file
+ * in each library block:
+ * LOCAL_CFLAGS := -funwind-tables -Wl,--no-merge-exidx-entries
+ *
+ * Example:
+ * COFFEE_TRY_JNI(env, *retcode = call_dangerous_function(env, object));
+ *
+ * Implementation notes:
+ *
+ * Currently the library is installing both alternate stack and signal
+ * handlers for known signals (SIGABRT, SIGILL, SIGTRAP, SIGBUS, SIGFPE,
+ * SIGSEGV, SIGSTKFLT), and is using sigsetjmp()/siglongjmp() to return to
+ * "userland" (compared to signal handler context). As a security, an alarm
+ * is started as soon as a fatal signal is detected (ie. not something the
+ * JVM will handle) to kill the process after a grace period. Be sure your
+ * program will exit quickly after the error is caught, or call alarm(0)
+ * to cancel the pending time-bomb.
+ * The signal handlers had to be written with caution, because the virtual
+ * machine might be using signals (including SEGV) to handle JIT compiler,
+ * and some clever optimizations (such as NullPointerException handling)
+ * We are using several signal-unsafe functions, namely:
+ * - siglongjmp() to return to userland
+ * - pthread_getspecific() to get thread-specific setup
+ *
+ * License:
+ *
+ * Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. 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 OWNER 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.
+ */
+
+#ifndef COFFEECATCH_JNI_H
+#define COFFEECATCH_JNI_H
+
+#include <jni.h>
+
+#include <coffeecatch/coffeecatch.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Setup crash handler to enter in a protected section. If a recognized signal
+ * is received in this section, an appropriate native Java Error will be
+ * raised.
+ *
+ * You can not exit the protected section block CODE_TO_BE_EXECUTED, using
+ * statements such as "return", because the cleanup code would not be
+ * executed.
+ *
+ * It is advised to enclose the complete CODE_TO_BE_EXECUTED block in a
+ * dedicated function declared extern or __attribute__ ((noinline)).
+ *
+ * You must build all your libraries with `-funwind-tables', to get proper
+ * unwinding information on all binaries. On Android, this can be achieved
+ * by using this line in the Android.mk file in each library block:
+ * LOCAL_CFLAGS := -funwind-tables
+ *
+ * Example:
+ *
+ * void my_native_function(JNIEnv* env, jobject object, jint *retcode) {
+ * COFFEE_TRY_JNI(env, *retcode = call_dangerous_function(env, object));
+ * }
+ *
+ * In addition, the following restrictions MUST be followed:
+ * - the function must be declared extern, or with the special attribute
+ * __attribute__ ((noinline)).
+ * - you must not use local variables before the COFFEE_TRY_JNI block,
+ * or define them as "volatile".
+ *
+COFFEE_TRY_JNI(JNIEnv* env, CODE_TO_BE_EXECUTED)
+ */
+
+/** Internal functions & definitions, not to be used directly. **/
+extern void coffeecatch_throw_exception(JNIEnv* env);
+#define COFFEE_TRY_JNI(ENV, CODE) \
+ do { \
+ COFFEE_TRY() { \
+ CODE; \
+ } COFFEE_CATCH() { \
+ coffeecatch_throw_exception(ENV); \
+ } COFFEE_END(); \
+ } while(0)
+/** End of internal functions & definitions. **/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/mbgl/android/jni.hpp b/include/mbgl/android/jni.hpp
new file mode 100644
index 0000000000..31772cf59c
--- /dev/null
+++ b/include/mbgl/android/jni.hpp
@@ -0,0 +1,40 @@
+#ifndef MBGL_ANDROID_JNI
+#define MBGL_ANDROID_JNI
+
+#include <string>
+#include <jni.h>
+
+namespace mbgl {
+namespace android {
+
+extern std::string cachePath;
+extern std::string dataPath;
+extern std::string apkPath;
+
+extern jmethodID onMapChangedId;
+extern jmethodID onFpsChangedId;
+
+extern jclass lonLatClass;
+extern jmethodID lonLatConstructorId;
+extern jfieldID lonLatLonId;
+extern jfieldID lonLatLatId;
+
+extern jclass lonLatZoomClass;
+extern jmethodID lonLatZoomConstructorId;
+extern jfieldID lonLatZoomLonId;
+extern jfieldID lonLatZoomLatId;
+extern jfieldID lonLatZoomZoomId;
+
+extern jclass runtimeExceptionClass;
+extern jclass nullPointerExceptionClass;;
+
+extern jmethodID listToArrayId;
+
+extern jclass arrayListClass;
+extern jmethodID arrayListConstructorId;
+extern jmethodID arrayListAddId;
+
+}
+}
+
+#endif
diff --git a/include/mbgl/android/native_map_view.hpp b/include/mbgl/android/native_map_view.hpp
new file mode 100644
index 0000000000..f8cf34d3b4
--- /dev/null
+++ b/include/mbgl/android/native_map_view.hpp
@@ -0,0 +1,86 @@
+#ifndef MBGL_ANDROID_NATIVE_MAP_VIEW
+#define MBGL_ANDROID_NATIVE_MAP_VIEW
+
+#include <mbgl/map/map.hpp>
+#include <mbgl/map/view.hpp>
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/storage/caching_http_file_source.hpp>
+
+#include <string>
+#include <jni.h>
+#include <android/native_window.h>
+#include <EGL/egl.h>
+
+namespace mbgl {
+namespace android {
+
+class NativeMapView : public mbgl::View, private mbgl::util::noncopyable {
+public:
+ NativeMapView(JNIEnv* env, jobject obj);
+ virtual ~NativeMapView();
+
+ void make_active() override;
+ void make_inactive() override;
+
+ void swap() override;
+
+ void notify() override;
+ void notify_map_change(mbgl::MapChange change, mbgl::timestamp delay) override;
+
+ mbgl::Map& getMap();
+ mbgl::CachingHTTPFileSource& getFileSource();
+
+ bool initializeDisplay();
+ void terminateDisplay();
+
+ bool initializeContext();
+ void terminateContext();
+
+ bool createSurface(ANativeWindow* window);
+ void destroySurface();
+
+ void start();
+ void stop();
+
+ void resume();
+ void pause(bool waitForPause = false);
+
+ void enableFps(bool enable);
+ void updateFps();
+
+private:
+ EGLConfig chooseConfig(const EGLConfig configs[], EGLint numConfigs);
+
+ void loadExtensions();
+
+private:
+ JavaVM* vm = nullptr;
+ jobject obj = nullptr;
+
+ ANativeWindow* window = nullptr;
+
+ mbgl::CachingHTTPFileSource fileSource;
+ mbgl::Map map;
+
+ EGLDisplay display = EGL_NO_DISPLAY;
+ EGLSurface surface = EGL_NO_SURFACE;
+ EGLContext context = EGL_NO_CONTEXT;
+
+ EGLConfig config = nullptr;
+ EGLint format = -1;
+
+ std::string styleUrl;
+ std::string apiKey;
+
+ bool firstTime = false;
+
+ bool usingDepth24 = false;
+
+ bool fpsEnabled = false;
+ double fps = 0.0;
+};
+
+}
+}
+
+#endif
diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp
index 245aaf9ea7..750dbbe023 100644
--- a/include/mbgl/map/map.hpp
+++ b/include/mbgl/map/map.hpp
@@ -13,6 +13,8 @@
#include <iosfwd>
#include <set>
#include <vector>
+#include <mutex>
+#include <condition_variable>
#include <functional>
namespace mbgl {
@@ -37,7 +39,7 @@ public:
~Map();
// Start the map render thread. It is asynchronous.
- void start();
+ void start(bool startPaused = false);
// Stop the map render thread. This call will block until the map rendering thread stopped.
// The optional callback function will be invoked repeatedly until the map thread is stopped.
@@ -45,6 +47,12 @@ public:
// this will be a busy waiting loop.
void stop(std::function<void ()> callback = std::function<void ()>());
+ // Pauses the render thread. The render thread will stop running but will not be terminated and will not lose state until resumed.
+ void pause(bool waitForPause = false);
+
+ // Resumes a paused render thread
+ void resume();
+
// Runs the map event loop. ONLY run this function when you want to get render a single frame
// with this map object. It will *not* spawn a separate thread and instead block until the
// frame is completely rendered.
@@ -65,13 +73,14 @@ public:
// Size
void resize(uint16_t width, uint16_t height, float ratio = 1);
- void resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height);
+ void resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight);
// Styling
void setAppliedClasses(const std::vector<std::string> &classes);
void toggleClass(const std::string &name);
const std::vector<std::string> &getAppliedClasses() const;
void setDefaultTransitionDuration(uint64_t milliseconds = 0);
+ uint64_t getDefaultTransitionDuration();
void setStyleURL(const std::string &url);
void setStyleJSON(std::string newStyleJSON, const std::string &base = "");
std::string getStyleJSON() const;
@@ -122,6 +131,9 @@ private:
util::ptr<Sprite> getSprite();
uv::worker& getWorker();
+ // Checks if render thread needs to pause
+ void checkForPause();
+
// Setup
void setup();
@@ -143,6 +155,14 @@ private:
std::unique_ptr<uv::async> asyncTerminate;
std::unique_ptr<uv::async> asyncRender;
+ bool terminating = false;
+ bool pausing = false;
+ bool isPaused = false;
+ std::mutex mutexRun;
+ std::condition_variable condRun;
+ std::mutex mutexPause;
+ std::condition_variable condPause;
+
// If cleared, the next time the render thread attempts to render the map, it will *actually*
// render the map.
std::atomic_flag isClean = ATOMIC_FLAG_INIT;
@@ -182,6 +202,11 @@ private:
std::string styleURL;
std::string styleJSON = "";
+ std::atomic_uint_fast64_t defaultTransitionDuration;
+
+ util::ptr<std::vector<std::string>> appliedClasses;
+ std::mutex appliedClassesMutex;
+
bool debug = false;
timestamp animationTime = 0;
diff --git a/include/mbgl/platform/android/log_android.hpp b/include/mbgl/platform/android/log_android.hpp
new file mode 100644
index 0000000000..94d90a9b36
--- /dev/null
+++ b/include/mbgl/platform/android/log_android.hpp
@@ -0,0 +1,24 @@
+#ifndef MBGL_PLATFORM_ANDROID_LOG_ANDROID
+#define MBGL_PLATFORM_ANDROID_LOG_ANDROID
+
+#include <mbgl/platform/log.hpp>
+
+namespace mbgl {
+
+class AndroidLogBackend : public LogBackend {
+private:
+ int severityToPriority(EventSeverity severity);
+
+public:
+ inline ~AndroidLogBackend() = default;
+
+ void record(EventSeverity severity, Event event, const std::string &msg);
+ void record(EventSeverity severity, Event event, const char* format, ...);
+ void record(EventSeverity severity, Event event, int64_t code);
+ void record(EventSeverity severity, Event event, int64_t code, const std::string &msg);
+};
+
+
+}
+
+#endif
diff --git a/include/mbgl/platform/default/headless_view.hpp b/include/mbgl/platform/default/headless_view.hpp
index 9d8acf70f7..f3b897e760 100644
--- a/include/mbgl/platform/default/headless_view.hpp
+++ b/include/mbgl/platform/default/headless_view.hpp
@@ -59,6 +59,7 @@ private:
GLuint fbo = 0;
GLuint fbo_depth_stencil = 0;
GLuint fbo_color = 0;
+ bool usingGl3OrNewer = false;
};
}
diff --git a/include/mbgl/platform/event.hpp b/include/mbgl/platform/event.hpp
index df2d8b03e9..5fd64119cc 100644
--- a/include/mbgl/platform/event.hpp
+++ b/include/mbgl/platform/event.hpp
@@ -32,7 +32,11 @@ enum class Event : uint8_t {
Database,
HttpRequest,
Sprite,
+ Image,
OpenGL,
+ JNI,
+ Android,
+ Crash
};
MBGL_DEFINE_ENUM_CLASS(EventClass, Event, {
@@ -45,7 +49,11 @@ MBGL_DEFINE_ENUM_CLASS(EventClass, Event, {
{ Event::Database, "Database" },
{ Event::HttpRequest, "HttpRequest" },
{ Event::Sprite, "Sprite" },
+ { Event::Image, "Image" },
{ Event::OpenGL, "OpenGL" },
+ { Event::JNI, "JNI" },
+ { Event::Android, "Android" },
+ { Event::Crash, "Crash" },
{ Event(-1), "Unknown" },
});
diff --git a/include/mbgl/platform/gl.hpp b/include/mbgl/platform/gl.hpp
index 6a35ab8006..cb0fb4892c 100644
--- a/include/mbgl/platform/gl.hpp
+++ b/include/mbgl/platform/gl.hpp
@@ -17,7 +17,8 @@
#else
#error Unsupported Apple platform
#endif
-#elif MBGL_USE_GLES2
+#elif __ANDROID__
+ #define GL_GLEXT_PROTOTYPES
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#else
@@ -129,6 +130,14 @@ extern PFNGLDELETEVERTEXARRAYSPROC DeleteVertexArrays;
extern PFNGLGENVERTEXARRAYSPROC GenVertexArrays;
extern PFNGLISVERTEXARRAYPROC IsVertexArray;
+// GL_EXT_packed_depth_stencil / GL_OES_packed_depth_stencil
+extern bool isPackedDepthStencilSupported;
+#define GL_DEPTH24_STENCIL8 0x88F0
+
+// GL_OES_depth24
+extern bool isDepth24Supported;
+#define GL_DEPTH_COMPONENT24 0x81A6
+
// GL_ARB_get_program_binary / GL_OES_get_program_binary
#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257
#define GL_PROGRAM_BINARY_LENGTH 0x8741
@@ -167,7 +176,6 @@ inline void start_group(const std::string &) {}
inline void end_group() {}
#endif
-
struct group {
inline group(const std::string &str) { start_group(str); }
~group() { end_group(); };
diff --git a/include/mbgl/storage/asset_request_baton.hpp b/include/mbgl/storage/asset_request_baton.hpp
new file mode 100644
index 0000000000..2cbb6c51c0
--- /dev/null
+++ b/include/mbgl/storage/asset_request_baton.hpp
@@ -0,0 +1,38 @@
+#ifndef MBGL_STORAGE_ASSET_REQUEST_BATON
+#define MBGL_STORAGE_ASSET_REQUEST_BATON
+
+#include <mbgl/util/uv.hpp>
+#include <thread>
+
+#include <uv.h>
+
+namespace mbgl {
+
+class AssetRequest;
+
+struct AssetRequestBaton {
+ AssetRequestBaton(AssetRequest *request_, const std::string &path, uv_loop_t *loop);
+
+ const std::thread::id threadId;
+ AssetRequest *request = nullptr;
+ std::unique_ptr<uv::async> asyncRun;
+ std::string path;
+ bool canceled = false;
+
+ void cancel();
+ static void notifyError(AssetRequestBaton *ptr, const int code, const char *message);
+ static void notifySuccess(AssetRequestBaton *ptr, const std::string body);
+ static void cleanup(AssetRequestBaton *ptr);
+
+ // IMPLEMENT THIS PLATFORM SPECIFIC FUNCTION:
+
+ // Called to load the asset. Platform-specific implementation.
+ static void run(AssetRequestBaton *ptr);
+
+};
+
+
+}
+
+
+#endif
diff --git a/include/mbgl/storage/caching_http_file_source.hpp b/include/mbgl/storage/caching_http_file_source.hpp
index 6d7d852d2a..655afa6396 100644
--- a/include/mbgl/storage/caching_http_file_source.hpp
+++ b/include/mbgl/storage/caching_http_file_source.hpp
@@ -29,6 +29,9 @@ public:
// Set the Mapbox API access token
void setAccessToken(std::string);
+ // Get the Mapbox API access token
+ std::string getAccessToken() const;
+
std::unique_ptr<Request> request(ResourceType type, const std::string &url);
void prepare(std::function<void()> fn);
@@ -37,7 +40,7 @@ public:
void setReachability(bool reachable);
private:
- std::thread::id thread_id;
+ std::thread::id threadId;
// Mapbox API access token.
std::string accessToken;
diff --git a/include/mbgl/storage/http_request_baton.hpp b/include/mbgl/storage/http_request_baton.hpp
index 545f9e236c..11abfb71d4 100644
--- a/include/mbgl/storage/http_request_baton.hpp
+++ b/include/mbgl/storage/http_request_baton.hpp
@@ -48,7 +48,7 @@ struct HTTPRequestBaton {
HTTPRequestBaton(const std::string &path);
~HTTPRequestBaton();
- const std::thread::id thread_id;
+ const std::thread::id threadId;
const std::string path;
HTTPRequest *request = nullptr;
diff --git a/include/mbgl/util/uv.hpp b/include/mbgl/util/uv.hpp
index 74ed9c87a8..f59037c1d8 100644
--- a/include/mbgl/util/uv.hpp
+++ b/include/mbgl/util/uv.hpp
@@ -16,6 +16,8 @@ class rwlock;
class loop;
class async;
class worker;
+class mutex;
+class cond;
}
diff --git a/platform/android/asset_request_baton_libzip.cpp b/platform/android/asset_request_baton_libzip.cpp
new file mode 100644
index 0000000000..8984aa58de
--- /dev/null
+++ b/platform/android/asset_request_baton_libzip.cpp
@@ -0,0 +1,93 @@
+#include <mbgl/mbgl.hpp>
+#include <mbgl/android/jni.hpp>
+#include <mbgl/storage/asset_request_baton.hpp>
+#include <mbgl/storage/response.hpp>
+#include <mbgl/util/std.hpp>
+
+#include <cerrno>
+// NOTE a bug in the Android NDK breaks std::errno
+// See https://code.google.com/p/android/issues/detail?id=72349
+// After this is fixed change usage errno to std::errno
+#include <zip.h>
+
+namespace mbgl {
+
+void AssetRequestBaton::run(AssetRequestBaton *ptr) {
+ assert(std::this_thread::get_id() == ptr->threadId);
+
+ if (ptr->canceled || !ptr->request) {
+ // Either the AssetRequest object has been destructed, or the
+ // request was canceled.
+ cleanup(ptr);
+ return;
+ }
+
+ int error = 0;
+ struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error);
+ if ((apk == nullptr) || ptr->canceled || !ptr->request) {
+ // Opening the APK failed or was canceled. There isn't much left we can do.
+ const int messageSize = zip_error_to_str(nullptr, 0, error, errno);
+ const std::unique_ptr<char[]> message = mbgl::util::make_unique<char[]>(messageSize);
+ zip_error_to_str(message.get(), 0, error, errno);
+ notifyError(ptr, 500, message.get());
+ cleanup(ptr);
+ return;
+ }
+
+ std::string apkFilePath = "assets/" + ptr->path;
+ struct zip_file *apkFile = zip_fopen(apk, apkFilePath.c_str(), ZIP_FL_NOCASE);
+ if ((apkFile == nullptr) || ptr->canceled || !ptr->request) {
+ // Opening the asset failed or was canceled. We already have an open file handle
+ // though, which we'll have to close.
+ zip_error_get(apk, &error, nullptr);
+ notifyError(ptr, error == ZIP_ER_NOENT ? 404 : 500, zip_strerror(apk));
+ zip_close(apk);
+ apk = nullptr;
+ cleanup(ptr);
+ return;
+ }
+
+ struct zip_stat stat;
+ if ((zip_stat(apk, apkFilePath.c_str(), ZIP_FL_NOCASE, &stat) != 0) || ptr->canceled || !ptr->request) {
+ // Stating failed or was canceled. We already have an open file handle
+ // though, which we'll have to close.
+ notifyError(ptr, 500, zip_strerror(apk));
+ zip_fclose(apkFile);
+ apkFile = nullptr;
+ zip_close(apk);
+ apk = nullptr;
+ cleanup(ptr);
+ return;
+ }
+
+ const std::unique_ptr<char[]> data = mbgl::util::make_unique<char[]>(stat.size);
+
+ if (static_cast<zip_uint64_t>(zip_fread(apkFile, reinterpret_cast<void *>(data.get()), stat.size)) != stat.size || ptr->canceled || !ptr->request) {
+ // Reading failed or was canceled. We already have an open file handle
+ // though, which we'll have to close.
+ notifyError(ptr, 500, zip_file_strerror(apkFile));
+ zip_fclose(apkFile);
+ apkFile = nullptr;
+ zip_close(apk);
+ apk = nullptr;
+ cleanup(ptr);
+ return;
+ }
+
+ std::string body(data.get(), stat.size);
+ notifySuccess(ptr, body);
+
+ if (zip_fclose(apkFile) != 0) {
+ // Closing the asset failed. But there isn't anything we can do.
+ }
+ apkFile = nullptr;
+
+ if (zip_close(apk) != 0) {
+ // Closing the APK failed. But there isn't anything we can do.
+ }
+ apk = nullptr;
+
+ cleanup(ptr);
+}
+
+}
diff --git a/platform/android/cache_database_data.cpp b/platform/android/cache_database_data.cpp
new file mode 100644
index 0000000000..2fefcdc4a3
--- /dev/null
+++ b/platform/android/cache_database_data.cpp
@@ -0,0 +1,13 @@
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/android/jni.hpp>
+
+namespace mbgl {
+namespace platform {
+
+// Returns the path to the default cache database on this system.
+std::string defaultCacheDatabase() {
+ return mbgl::android::cachePath + "/mbgl-cache.db";
+}
+
+}
+}
diff --git a/platform/android/log_android.cpp b/platform/android/log_android.cpp
new file mode 100644
index 0000000000..5e40ce33bd
--- /dev/null
+++ b/platform/android/log_android.cpp
@@ -0,0 +1,59 @@
+#include <mbgl/platform/android/log_android.hpp>
+
+#include <iostream>
+#include <cstdarg>
+#define __STDC_FORMAT_MACROS // NDK bug workaround: https://code.google.com/p/android/issues/detail?id=72349
+#include <cinttypes>
+
+#include <android/log.h>
+
+namespace mbgl {
+
+int AndroidLogBackend::severityToPriority(EventSeverity severity) {
+ switch(severity) {
+ case EventSeverity::Debug:
+ return ANDROID_LOG_DEBUG;
+
+ case EventSeverity::Info:
+ return ANDROID_LOG_INFO;
+
+ case EventSeverity::Warning:
+ return ANDROID_LOG_WARN;
+
+ case EventSeverity::Error:
+ return ANDROID_LOG_ERROR;
+
+ default:
+ return ANDROID_LOG_VERBOSE;
+ }
+}
+
+void AndroidLogBackend::record(EventSeverity severity, Event event, const std::string &msg) {
+ __android_log_print(severityToPriority(severity), EventClass(event).c_str(), "%s", msg.c_str());
+}
+
+void AndroidLogBackend::record(EventSeverity severity, Event event, const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+
+ const int len = vsnprintf(nullptr, 0, format, args) + 1;
+ char* buf = new char[len];
+ vsnprintf(buf, len, format, args);
+
+ va_end(args);
+
+ __android_log_print(severityToPriority(severity), EventClass(event).c_str(), "%s", buf);
+
+ delete buf;
+ buf = nullptr;
+}
+
+void AndroidLogBackend::record(EventSeverity severity, Event event, int64_t code) {
+ __android_log_print(severityToPriority(severity), EventClass(event).c_str(), "(%" PRId64 ")", code);
+}
+
+void AndroidLogBackend::record(EventSeverity severity, Event event, int64_t code, const std::string &msg) {
+ __android_log_print(severityToPriority(severity), EventClass(event).c_str(), "(%" PRId64 ") %s", code, msg.c_str());
+}
+
+}
diff --git a/platform/darwin/http_request_baton_cocoa.mm b/platform/darwin/http_request_baton_cocoa.mm
index 1c256d0ba8..472154dc2f 100644
--- a/platform/darwin/http_request_baton_cocoa.mm
+++ b/platform/darwin/http_request_baton_cocoa.mm
@@ -15,7 +15,7 @@ dispatch_once_t request_initialize = 0;
NSURLSession *session = nullptr;
void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) {
- assert(std::this_thread::get_id() == ptr->thread_id);
+ assert(std::this_thread::get_id() == ptr->threadId);
// Starts the request.
util::ptr<HTTPRequestBaton> baton = ptr;
@@ -131,7 +131,7 @@ void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) {
}
void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &ptr) {
- assert(std::this_thread::get_id() == ptr->thread_id);
+ assert(std::this_thread::get_id() == ptr->threadId);
assert(ptr->ptr);
NSURLSessionDataTask *task = CFBridgingRelease(ptr->ptr);
diff --git a/platform/default/asset_request_baton_noop.cpp b/platform/default/asset_request_baton_noop.cpp
new file mode 100644
index 0000000000..79f69a2daf
--- /dev/null
+++ b/platform/default/asset_request_baton_noop.cpp
@@ -0,0 +1,22 @@
+#include <mbgl/mbgl.hpp>
+#include <mbgl/storage/asset_request_baton.hpp>
+#include <mbgl/storage/response.hpp>
+
+namespace mbgl {
+
+void AssetRequestBaton::run(AssetRequestBaton *ptr) {
+ assert(std::this_thread::get_id() == ptr->threadId);
+
+ if (ptr->canceled || !ptr->request) {
+ // Either the AssetRequest object has been destructed, or the
+ // request was canceled.
+ cleanup(ptr);
+ return;
+ }
+
+ // Just return a 500 error until implemented properly
+ notifyError(ptr, 500, "Assets not implemented on this platform.");
+ cleanup(ptr);
+}
+
+}
diff --git a/platform/default/glfw_view.cpp b/platform/default/glfw_view.cpp
index 32b7e44ece..2ac047b8a1 100644
--- a/platform/default/glfw_view.cpp
+++ b/platform/default/glfw_view.cpp
@@ -66,7 +66,7 @@ void GLFWView::initialize(mbgl::Map *map_) {
glfwSetKeyCallback(window, key);
- const std::string extensions = (char *)glGetString(GL_EXTENSIONS);
+ const std::string extensions = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
{
using namespace mbgl;
@@ -152,6 +152,10 @@ void GLFWView::initialize(mbgl::Map *map_) {
assert(gl::ProgramBinary != nullptr);
assert(gl::ProgramParameteri != nullptr);
}
+
+ // Require packed depth stencil
+ gl::isPackedDepthStencilSupported = true;
+ gl::isDepth24Supported = true;
}
glfwMakeContextCurrent(nullptr);
diff --git a/platform/default/headless_view.cpp b/platform/default/headless_view.cpp
index 5b95c76026..0041fb3bbb 100644
--- a/platform/default/headless_view.cpp
+++ b/platform/default/headless_view.cpp
@@ -51,7 +51,23 @@ HeadlessView::HeadlessView(std::shared_ptr<HeadlessDisplay> display)
void HeadlessView::loadExtensions() {
make_active();
- const std::string extensions = (char *)glGetString(GL_EXTENSIONS);
+
+ std::string extensions;
+ if (usingGl3OrNewer) {
+#ifdef GL_VERSION_3_0
+ GLuint num_extensions = 0;
+ glGetIntegerv(GL_NUM_EXTENSIONS, reinterpret_cast<GLint *>(&num_extensions));
+ for (GLuint i = 0; i < num_extensions; i++) {
+ extensions.append(reinterpret_cast<const char *>(glGetStringi(GL_EXTENSIONS, i)));
+ extensions.append(" ");
+ }
+#else
+ throw std::runtime_error("Using GL 3.0+ context with out correct headers.\n");
+#endif
+ } else {
+ extensions = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
+ }
+
#ifdef MBGL_USE_CGL
if (extensions.find("GL_APPLE_vertex_array_object") != std::string::npos) {
@@ -69,6 +85,11 @@ void HeadlessView::loadExtensions() {
gl::IsVertexArray = (gl::PFNGLISVERTEXARRAYPROC)glXGetProcAddress((const GLubyte *)"glIsVertexArrayARB");
}
#endif
+
+ // HeadlessView requires packed depth stencil
+ gl::isPackedDepthStencilSupported = true;
+ gl::isDepth24Supported = true;
+
make_inactive();
}
@@ -92,7 +113,7 @@ static const core_profile_version core_profile_versions[] = {
{0, 0},
};
-GLXContext createCoreProfile(Display *dpy, GLXFBConfig fbconfig) {
+GLXContext createCoreProfile(Display *dpy, GLXFBConfig fbconfig, bool& usingGl3OrNewer) {
static bool context_creation_failed = false;
GLXContext ctx = 0;
@@ -105,7 +126,8 @@ GLXContext createCoreProfile(Display *dpy, GLXFBConfig fbconfig) {
});
// Try to create core profiles from the highest known version on down.
- for (int i = 0; !ctx && core_profile_versions[i].major; i++) {
+ int i = 0;
+ for (; !ctx && core_profile_versions[i].major; i++) {
context_creation_failed = false;
const int context_flags[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, core_profile_versions[i].major,
@@ -118,6 +140,10 @@ GLXContext createCoreProfile(Display *dpy, GLXFBConfig fbconfig) {
}
}
+ if (core_profile_versions[i].major >= 3) {
+ usingGl3OrNewer = true;
+ }
+
// Restore the old error handler.
XSetErrorHandler(previous_error_handler);
return ctx;
@@ -151,11 +177,11 @@ void HeadlessView::createContext() {
#ifdef GLX_ARB_create_context
if (glXCreateContextAttribsARB) {
// Try to create a core profile context.
- gl_context = createCoreProfile(x_display, fb_configs[0]);
+ gl_context = createCoreProfile(x_display, fb_configs[0], usingGl3OrNewer);
}
#endif
- if (!gl_context) {
+ if (gl_context == 0) {
// Try to create a legacy context
gl_context = glXCreateNewContext(x_display, fb_configs[0], GLX_RGBA_TYPE, 0, True);
if (gl_context) {
@@ -333,4 +359,3 @@ void HeadlessView::make_inactive() {
void HeadlessView::swap() {}
}
-
diff --git a/platform/default/http_request_baton_curl.cpp b/platform/default/http_request_baton_curl.cpp
index f05ee5acf9..74c6187e76 100644
--- a/platform/default/http_request_baton_curl.cpp
+++ b/platform/default/http_request_baton_curl.cpp
@@ -1,9 +1,16 @@
+#include <mbgl/mbgl.hpp>
#include <mbgl/storage/http_request_baton.hpp>
#include <mbgl/util/uv-messenger.h>
#include <mbgl/util/time.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/std.hpp>
+#ifdef __ANDROID__
+ #include <mbgl/android/jni.hpp>
+ #include <zip.h>
+ #include <openssl/ssl.h>
+#endif
+
#include <uv.h>
#include <curl/curl.h>
@@ -405,6 +412,103 @@ size_t curl_header_cb(char * const buffer, const size_t size, const size_t nmemb
return length;
}
+// This function is called to load the CA bundle
+// from http://curl.haxx.se/libcurl/c/cacertinmem.html
+#ifdef __ANDROID__
+static CURLcode sslctx_function(CURL */*curl*/, void *sslctx, void */*parm*/) {
+
+ int error = 0;
+ struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error);
+ if (apk == nullptr) {
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+
+ struct zip_file *apkFile = zip_fopen(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE);
+ if (apkFile == nullptr) {
+ zip_close(apk);
+ apk = nullptr;
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+
+ struct zip_stat stat;
+ if (zip_stat(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE, &stat) != 0) {
+ zip_fclose(apkFile);
+ apkFile = nullptr;
+ zip_close(apk);
+ apk = nullptr;
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+
+ if (stat.size > std::numeric_limits<int>::max()) {
+ zip_fclose(apkFile);
+ apkFile = nullptr;
+ zip_close(apk);
+ apk = nullptr;
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+
+ const std::unique_ptr<char[]> pem = util::make_unique<char[]>(stat.size);
+
+ if (static_cast<zip_uint64_t>(zip_fread(apkFile, reinterpret_cast<void *>(pem.get()), stat.size)) != stat.size) {
+ zip_fclose(apkFile);
+ apkFile = nullptr;
+ zip_close(apk);
+ apk = nullptr;
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+
+ // get a pointer to the X509 certificate store (which may be empty!)
+ X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx);
+ if (store == nullptr) {
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+
+ // get a BIO
+ BIO *bio = BIO_new_mem_buf(pem.get(), static_cast<int>(stat.size));
+ if (bio == nullptr) {
+ store = nullptr;
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+
+ // use it to read the PEM formatted certificate from memory into an X509
+ // structure that SSL can use
+ X509 *cert = nullptr;
+ while (PEM_read_bio_X509(bio, &cert, 0, nullptr) != nullptr) {
+ if (cert == nullptr) {
+ BIO_free(bio);
+ bio = nullptr;
+ store = nullptr;
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+
+ // add our certificate to this store
+ if (X509_STORE_add_cert(store, cert) == 0) {
+ X509_free(cert);
+ cert = nullptr;
+ BIO_free(bio);
+ bio = nullptr;
+ store = nullptr;
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+
+ X509_free(cert);
+ cert = nullptr;
+ }
+
+ // decrease reference counts
+ BIO_free(bio);
+ bio = nullptr;
+
+ zip_fclose(apkFile);
+ apkFile = nullptr;
+ zip_close(apk);
+ apk = nullptr;
+
+ // all set to go
+ return CURLE_OK;
+}
+#endif
+
// This function must run in the CURL thread.
void start_request(void *const ptr) {
assert(std::this_thread::get_id() == thread_id);
@@ -436,7 +540,12 @@ void start_request(void *const ptr) {
// Carry on the shared pointer in the private information of the CURL handle.
curl_easy_setopt(context->handle, CURLOPT_PRIVATE, context);
+#ifndef __ANDROID__
curl_easy_setopt(context->handle, CURLOPT_CAINFO, "ca-bundle.crt");
+#else
+ curl_easy_setopt(context->handle, CURLOPT_SSLCERTTYPE, "PEM");
+ curl_easy_setopt(context->handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function);
+#endif
curl_easy_setopt(context->handle, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(context->handle, CURLOPT_URL, context->baton->path.c_str());
curl_easy_setopt(context->handle, CURLOPT_WRITEFUNCTION, curl_write_cb);
@@ -444,7 +553,6 @@ void start_request(void *const ptr) {
curl_easy_setopt(context->handle, CURLOPT_HEADERFUNCTION, curl_header_cb);
curl_easy_setopt(context->handle, CURLOPT_HEADERDATA, &context->baton->response);
curl_easy_setopt(context->handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate");
- curl_easy_setopt(context->handle, CURLOPT_SHARE, share);
// Start requesting the information.
curl_multi_add_handle(multi, context->handle);
@@ -488,14 +596,14 @@ void create_thread() {
// This function must be run from the main thread (== where the HTTPRequestBaton was created)
void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) {
- assert(std::this_thread::get_id() == ptr->thread_id);
+ assert(std::this_thread::get_id() == ptr->threadId);
uv_once(&once, create_thread);
uv_messenger_send(&start_messenger, new util::ptr<HTTPRequestBaton>(ptr));
}
// This function must be run from the main thread (== where the HTTPRequestBaton was created)
void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &ptr) {
- assert(std::this_thread::get_id() == ptr->thread_id);
+ assert(std::this_thread::get_id() == ptr->threadId);
uv_once(&once, create_thread);
uv_messenger_send(&stop_messenger, new util::ptr<HTTPRequestBaton>(ptr));
}
diff --git a/platform/default/image.cpp b/platform/default/image.cpp
index d881b9231c..311aa2ed72 100644
--- a/platform/default/image.cpp
+++ b/platform/default/image.cpp
@@ -1,4 +1,5 @@
#include <mbgl/util/image.hpp>
+#include <mbgl/platform/log.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/std.hpp>
@@ -31,14 +32,14 @@ std::string compress_png(int width, int height, void *rgba) {
png_voidp error_ptr = 0;
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, error_ptr, NULL, NULL);
if (!png_ptr) {
- fprintf(stderr, "Couldn't create png_ptr\n");
+ Log::Error(Event::Image, "Couldn't create png_ptr");
return "";
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!png_ptr) {
png_destroy_write_struct(&png_ptr, (png_infopp)0);
- fprintf(stderr, "Couldn't create info_ptr\n");
+ Log::Error(Event::Image, "Couldn't create info_ptr");
return "";
}
diff --git a/scripts/travis_before_install.sh b/scripts/travis_before_install.sh
index 78c0799f60..3693be33a5 100755
--- a/scripts/travis_before_install.sh
+++ b/scripts/travis_before_install.sh
@@ -1,8 +1,71 @@
#!/usr/bin/env bash
+set -x
set -e
set -o pipefail
+if [[ "${MASON_PLATFORM}" == "android" ]]; then
+
+ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
+
+ echo "load submodules"
+ git submodule update --init --recursive
+
+ echo "fetching JDK"
+ wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/7u71-b14/jdk-7u71-linux-x64.tar.gz
+
+ echo "unpacking JDK"
+ tar -xzf ./jdk-7u71-linux-x64.tar.gz
+
+ echo "fetching SDK"
+ wget http://dl.google.com/android/android-sdk_r23.0.2-linux.tgz
+
+ echo "unpacking SDK"
+ tar -xzf ./android-sdk_r23.0.2-linux.tgz
+ mv ./android-sdk-linux ./android-sdk
+
+ echo "installing SDK"
+ sudo apt-get -y install lib32stdc++6 lib32z1 jq
+ echo y | ./android-sdk/tools/android update sdk -u -a -t tools,platform-tools,build-tools-21.1.1,android-21,extra-android-m2repository,extra-google-m2repository
+
+ echo "inserting access token"
+ sed -i "s/access token goes here/${ANDROID_KEY}/g" android/java/app/src/main/java/com/mapbox/mapboxgl/app/MapFragment.java
+
+elif [[ ${TRAVIS_OS_NAME} == "osx" ]]; then
+
+ echo "installing 7z"
+ MASON_PLATFORM= ./.mason/mason install 7z 9.20
+
+ echo "setting 7z path"
+ SEVEN_ZIP_PATH="$(MASON_PLATFORM= ./.mason/mason prefix 7z 9.20)/bin/7za"
+
+ echo "fetching NDK"
+ wget http://dl.google.com/android/ndk/android-ndk-r10c-darwin-x86_64.bin
+
+ echo "chmod NDK"
+ chmod a+x ./android-ndk-r10c-darwin-x86_64.bin
+
+ echo "unpacking NDK"
+ $SEVEN_ZIP_PATH x ./android-ndk-r10c-darwin-x86_64.bin > .tmp-ndk-log
+ rm .tmp-ndk-log
+
+ echo "fetching SDK"
+ wget http://dl.google.com/android/android-sdk_r23.0.2-macosx.zip
+
+ echo "unpacking SDK"
+ unzip -qq android-sdk_r23.0.2-macosx.zip
+ mv ./android-sdk-macosx ./android-sdk
+
+ echo "installing SDK"
+ echo y | ./android-sdk/tools/android update sdk -u -a -t tools,platform-tools,build-tools-21.1.1,android-21,extra-android-m2repository,extra-google-m2repository
+
+ echo "inserting access token"
+ sed -i "s/access token goes here/${ANDROID_KEY}/g" android/java/app/src/main/java/com/mapbox/mapboxgl/app/MapFragment.java
+
+ fi
+
+fi
+
if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
#
# install Linux dependencies
@@ -12,30 +75,29 @@ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
fi
sudo add-apt-repository --yes ppa:boost-latest/ppa
- mapbox_time "apt_update" \
+ echo "apt_update"
sudo apt-get update -y
- mapbox_time "install_gcc" \
+ echo "install_gcc"
sudo apt-get -y install gcc-4.8 g++-4.8
- mapbox_time "install_build_tools" \
+ echo "install_build_tools"
sudo apt-get -y install git build-essential zlib1g-dev automake gdb \
libtool xutils-dev make cmake pkg-config python-pip \
libboost1.55-dev libcurl4-openssl-dev \
libpng-dev libsqlite3-dev
- mapbox_time "install_opengl" \
+ echo "install_opengl"
sudo apt-get -y install mesa-utils libxi-dev x11proto-randr-dev \
x11proto-xext-dev libxrandr-dev \
x11proto-xf86vidmode-dev libxxf86vm-dev \
libxcursor-dev libxinerama-dev \
llvm-3.4 # required for mesa
+ echo "install_mesa"
+ MASON_PLATFORM= mason install mesa 10.3.1
- mapbox_time "install_mesa" \
- mason install mesa 10.3.1
-
- mapbox_time "install_awscli" \
+ echo "install_awscli"
sudo pip install awscli
elif [[ ${TRAVIS_OS_NAME} == "osx" ]]; then
#
diff --git a/scripts/travis_helper.sh b/scripts/travis_helper.sh
index 040ef29443..c42ce215e3 100755
--- a/scripts/travis_helper.sh
+++ b/scripts/travis_helper.sh
@@ -32,6 +32,26 @@ function mapbox_time {
mapbox_time_finish $name $timer_id
}
+if [[ "${TRAVIS_COMMIT:-false}" == false ]]; then
+function travis_fold {
+ local action=$1
+ local name=$2
+ echo -en "travis_fold:${action}:${name}\r${ANSI_CLEAR}"
+}
+function travis_nanoseconds {
+ local cmd="date"
+ local format="+%s%N"
+ local os=$(uname)
+
+ if hash gdate > /dev/null 2>&1; then
+ cmd="gdate" # use gdate if available
+ elif [[ "$os" = Darwin ]]; then
+ format="+%s000000000" # fallback to second precision on darwin (does not support %N)
+ fi
+
+ $cmd -u $format
+}
+fi
export ANSI_CLEAR
export -f travis_fold
diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh
index ea6097dc55..6fc9edd92b 100755
--- a/scripts/travis_script.sh
+++ b/scripts/travis_script.sh
@@ -6,7 +6,17 @@ set -o pipefail
mapbox_time "checkout_styles" \
git submodule update --init styles
-if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
+if [[ $MASON_PLATFORM == "android" ]]; then
+ mapbox_time "compile_program" \
+ make android -j$JOBS BUILDTYPE=${BUILDTYPE}
+
+ if [[ $TESTMUNK == "yes" ]]; then
+ mapbox_time_start "upload_testmunk"
+ (cd ./android/test/ && ./upload_testmunk.sh ../java/app/build/outputs/apk/app-debug.apk)
+ mapbox_time_finish
+ fi
+
+elif [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
#
# build & test Linux
#
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
index f46e0f558b..5eed5ad69a 100644
--- a/src/mbgl/map/map.cpp
+++ b/src/mbgl/map/map.cpp
@@ -23,6 +23,7 @@
#include <mbgl/storage/file_source.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/util/string.hpp>
+#include <mbgl/util/uv.hpp>
#include <algorithm>
#include <iostream>
@@ -32,6 +33,10 @@
#include <uv.h>
+#ifdef __ANDROID__
+ #include <coffeecatch/coffeecatch.h>
+#endif
+
// Check libuv library version.
const static bool uv_version_check = []() {
const unsigned int version = uv_version();
@@ -130,7 +135,7 @@ uv::worker &Map::getWorker() {
return *workers;
}
-void Map::start() {
+void Map::start(bool startPaused) {
assert(std::this_thread::get_id() == mainThread);
assert(!async);
@@ -152,6 +157,8 @@ void Map::start() {
fileSource.clearLoop();
+ terminating = true;
+
// Closes all open handles on the loop. This means that the loop will automatically terminate.
asyncRender.reset();
asyncTerminate.reset();
@@ -176,6 +183,11 @@ void Map::start() {
}
});
+ // Do we need to pause first?
+ if (startPaused) {
+ pause();
+ }
+
thread = std::thread([this]() {
#ifndef NDEBUG
mapThread = std::this_thread::get_id();
@@ -204,6 +216,8 @@ void Map::stop(std::function<void ()> callback) {
asyncTerminate->send();
+ resume();
+
if (callback) {
// Wait until the render thread stopped. We are using this construct instead of plainly
// relying on the thread_join because the system might need to run things in the current
@@ -223,7 +237,39 @@ void Map::stop(std::function<void ()> callback) {
async = false;
}
+void Map::pause(bool waitForPause) {
+ assert(std::this_thread::get_id() == mainThread);
+ assert(async);
+ mutexRun.lock();
+ pausing = true;
+ mutexRun.unlock();
+
+ uv_stop(**loop);
+ rerender(); // Needed to ensure uv_stop is seen and uv_run exits, otherwise we deadlock on wait_for_pause
+
+ if (waitForPause) {
+ std::unique_lock<std::mutex> lockPause (mutexPause);
+ while (!isPaused) {
+ condPause.wait(lockPause);
+ }
+ }
+}
+
+void Map::resume() {
+ assert(std::this_thread::get_id() == mainThread);
+ assert(async);
+
+ mutexRun.lock();
+ pausing = false;
+ condRun.notify_all();
+ mutexRun.unlock();
+}
+
void Map::run() {
+#ifdef __ANDROID__
+ COFFEE_TRY() {
+#endif
+
#ifndef NDEBUG
if (!async) {
mapThread = mainThread;
@@ -231,9 +277,22 @@ void Map::run() {
#endif
assert(std::this_thread::get_id() == mapThread);
+ if (async) {
+ checkForPause();
+ }
+
setup();
prepare();
- uv_run(**loop, UV_RUN_DEFAULT);
+
+ if (async) {
+ terminating = false;
+ while(!terminating) {
+ uv_run(**loop, UV_RUN_DEFAULT);
+ checkForPause();
+ }
+ } else {
+ uv_run(**loop, UV_RUN_DEFAULT);
+ }
// Run the event loop once more to make sure our async delete handlers are called.
uv_run(**loop, UV_RUN_ONCE);
@@ -246,6 +305,32 @@ void Map::run() {
mapThread = std::thread::id();
#endif
}
+#ifdef __ANDROID__
+ } COFFEE_CATCH() {
+ Log::Error(Event::Crash, "Map::run() crash:\n%s", coffeecatch_get_message());
+ abort();
+ }
+#endif
+}
+
+void Map::checkForPause() {
+ std::unique_lock<std::mutex> lockRun (mutexRun);
+ while (pausing) {
+ view.make_inactive();
+
+ mutexPause.lock();
+ isPaused = true;
+ condPause.notify_all();
+ mutexPause.unlock();
+
+ condRun.wait(lockRun);
+
+ view.make_active();
+ }
+
+ mutexPause.lock();
+ isPaused = false;
+ mutexPause.unlock();
}
void Map::rerender() {
@@ -282,12 +367,16 @@ void Map::setup() {
assert(painter);
view.make_active();
painter->setup();
- view.make_inactive();
}
void Map::setStyleURL(const std::string &url) {
// TODO: Make threadsafe.
+
styleURL = url;
+ if (async) {
+ stop();
+ start();
+ }
}
@@ -298,9 +387,24 @@ void Map::setStyleJSON(std::string newStyleJSON, const std::string &base) {
if (!style) {
style = std::make_shared<Style>();
}
+
style->loadJSON((const uint8_t *)styleJSON.c_str());
fileSource.setBase(base);
glyphStore->setURL(style->glyph_url);
+
+ style->setDefaultTransitionDuration(defaultTransitionDuration);
+
+ // set applied classes if they were set while the style was loading
+ appliedClassesMutex.lock();
+ util::ptr<std::vector<std::string>> classes = appliedClasses;
+ if (appliedClasses) {
+ appliedClasses.reset();
+ }
+ appliedClassesMutex.unlock();
+ if (classes) {
+ style->setAppliedClasses(*classes);
+ }
+
update();
}
@@ -325,8 +429,8 @@ void Map::resize(uint16_t width, uint16_t height, float ratio) {
resize(width, height, ratio, width * ratio, height * ratio);
}
-void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height) {
- if (transform.resize(width, height, ratio, fb_width, fb_height)) {
+void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight) {
+ if (transform.resize(width, height, ratio, fbWidth, fbHeight)) {
update();
}
}
@@ -486,9 +590,15 @@ bool Map::getDebug() const {
}
void Map::setAppliedClasses(const std::vector<std::string> &classes) {
- style->setAppliedClasses(classes);
- if (style->hasTransitions()) {
- update();
+ if (style) {
+ style->setAppliedClasses(classes);
+ if (style->hasTransitions()) {
+ update();
+ }
+ }
+ else {
+ std::lock_guard<std::mutex> lock(appliedClassesMutex);
+ appliedClasses = mbgl::util::make_unique<std::vector<std::string>>(classes);
}
}
@@ -505,7 +615,14 @@ const std::vector<std::string> &Map::getAppliedClasses() const {
}
void Map::setDefaultTransitionDuration(uint64_t milliseconds) {
- style->setDefaultTransitionDuration(milliseconds);
+ defaultTransitionDuration = milliseconds;
+ if (style) {
+ style->setDefaultTransitionDuration(milliseconds);
+ }
+}
+
+uint64_t Map::getDefaultTransitionDuration() {
+ return defaultTransitionDuration;
}
void Map::updateSources() {
@@ -606,8 +723,6 @@ void Map::prepare() {
}
void Map::render() {
- view.make_active();
-
assert(painter);
painter->render(*style, activeSources,
state, animationTime);
@@ -615,6 +730,4 @@ void Map::render() {
if (transform.needsTransition() || style->hasTransitions()) {
update();
}
-
- view.make_inactive();
}
diff --git a/src/mbgl/platform/gl.cpp b/src/mbgl/platform/gl.cpp
index 4aace913d0..f99883a4e5 100644
--- a/src/mbgl/platform/gl.cpp
+++ b/src/mbgl/platform/gl.cpp
@@ -70,6 +70,10 @@ PFNGLDELETEVERTEXARRAYSPROC DeleteVertexArrays = nullptr;
PFNGLGENVERTEXARRAYSPROC GenVertexArrays = nullptr;
PFNGLISVERTEXARRAYPROC IsVertexArray = nullptr;
+bool isPackedDepthStencilSupported = false;
+
+bool isDepth24Supported = false;
+
PFNGLGETPROGRAMBINARYPROC GetProgramBinary = nullptr;
PFNGLPROGRAMBINARYPROC ProgramBinary = nullptr;
PFNGLPROGRAMPARAMETERIPROC ProgramParameteri = nullptr;
diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp
index c0c1fe2489..8cb456a7a6 100644
--- a/src/mbgl/renderer/painter.cpp
+++ b/src/mbgl/renderer/painter.cpp
@@ -45,11 +45,11 @@ void Painter::setup() {
// Enable GL debugging
if ((gl::DebugMessageControl != nullptr) && (gl::DebugMessageCallback != nullptr)) {
// This will enable all messages including performance hints
- gl::DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
+ //gl::DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
// This will only enable high and medium severity messages
- //gl::DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH, 0, nullptr, GL_TRUE);
- //gl::DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, GL_TRUE);
+ gl::DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH, 0, nullptr, GL_TRUE);
+ gl::DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, GL_TRUE);
gl::DebugMessageCallback(gl::debug_callback, nullptr);
}
diff --git a/src/mbgl/renderer/painter_prerender.cpp b/src/mbgl/renderer/painter_prerender.cpp
index f38470530b..22a2be9b64 100644
--- a/src/mbgl/renderer/painter_prerender.cpp
+++ b/src/mbgl/renderer/painter_prerender.cpp
@@ -10,7 +10,7 @@ void Painter::preparePrerender(RasterBucket &bucket) {
glDisable(GL_STENCIL_TEST);
// Render the actual tile.
-#if GL_EXT_discard_framebuffer
+#if GL_EXT_discard_framebuffer && !__ANDROID__
const GLenum discards[] = {GL_COLOR_ATTACHMENT0};
glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards);
#endif
diff --git a/src/mbgl/renderer/prerendered_texture.cpp b/src/mbgl/renderer/prerendered_texture.cpp
index dd35f11372..f17fe3b82a 100644
--- a/src/mbgl/renderer/prerendered_texture.cpp
+++ b/src/mbgl/renderer/prerendered_texture.cpp
@@ -3,6 +3,8 @@
#include <mbgl/renderer/painter.hpp>
#include <mbgl/style/style_bucket.hpp>
+#include <mbgl/platform/log.hpp>
+
using namespace mbgl;
PrerenderedTexture::PrerenderedTexture(const StyleBucketRaster &properties_)
@@ -15,6 +17,16 @@ PrerenderedTexture::~PrerenderedTexture() {
texture = 0;
}
+ if (fboDepth != 0) {
+ glDeleteRenderbuffers(1, &fboDepth);
+ fboDepth = 0;
+ }
+
+ if (fboStencil != 0) {
+ glDeleteRenderbuffers(1, &fboStencil);
+ fboStencil = 0;
+ }
+
if (fbo != 0) {
glDeleteFramebuffers(1, &fbo);
fbo = 0;
@@ -32,7 +44,7 @@ void PrerenderedTexture::bindTexture() {
}
void PrerenderedTexture::bindFramebuffer() {
- glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previous_fbo);
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFbo);
if (texture == 0) {
glGenTextures(1, &texture);
@@ -48,15 +60,29 @@ void PrerenderedTexture::bindFramebuffer() {
glBindTexture(GL_TEXTURE_2D, 0);
}
- if (fbo_depth_stencil == 0) {
- // Create depth/stencil buffer
- glGenRenderbuffers(1, &fbo_depth_stencil);
- glBindRenderbuffer(GL_RENDERBUFFER, fbo_depth_stencil);
-#ifdef GL_ES_VERSION_2_0
- glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, properties.size, properties.size);
-#else
- glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, properties.size, properties.size);
-#endif
+ if (fboDepth == 0) {
+ // Create depth buffer
+ glGenRenderbuffers(1, &fboDepth);
+ glBindRenderbuffer(GL_RENDERBUFFER, fboDepth);
+ if (gl::isPackedDepthStencilSupported) {
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, properties.size, properties.size);
+ } else {
+ if (gl::isDepth24Supported) {
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, properties.size, properties.size);
+ } else {
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, properties.size, properties.size);
+ }
+ }
+
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ }
+
+ if (!gl::isPackedDepthStencilSupported && (fboStencil == 0)) {
+ // Create stencil buffer
+ glGenRenderbuffers(1, &fboStencil);
+ glBindRenderbuffer(GL_RENDERBUFFER, fboStencil);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, properties.size, properties.size);
+
glBindRenderbuffer(GL_RENDERBUFFER, 0);
}
@@ -64,26 +90,32 @@ void PrerenderedTexture::bindFramebuffer() {
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+
+ if (gl::isPackedDepthStencilSupported) {
#ifdef GL_ES_VERSION_2_0
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo_depth_stencil);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo_depth_stencil);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fboDepth);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fboDepth);
#else
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo_depth_stencil);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fboDepth);
#endif
+ } else {
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fboDepth);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fboStencil);
+ }
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
- fprintf(stderr, "Couldn't create framebuffer: ");
+ mbgl::Log::Error(mbgl::Event::OpenGL, "Couldn't create framebuffer: ");
switch (status) {
- case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: fprintf(stderr, "incomplete attachment\n"); break;
- case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: fprintf(stderr, "incomplete missing attachment\n"); break;
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: mbgl::Log::Error(mbgl::Event::OpenGL, "incomplete attachment\n"); break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: mbgl::Log::Error(mbgl::Event::OpenGL, "incomplete missing attachment\n"); break;
#ifdef GL_ES_VERSION_2_0
- case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: fprintf(stderr, "incomplete dimensions\n"); break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: mbgl::Log::Error(mbgl::Event::OpenGL, "incomplete dimensions\n"); break;
#else
- case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: fprintf(stderr, "incomplete draw buffer\n"); break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: mbgl::Log::Error(mbgl::Event::OpenGL, "incomplete draw buffer\n"); break;
#endif
- case GL_FRAMEBUFFER_UNSUPPORTED: fprintf(stderr, "unsupported\n"); break;
- default: fprintf(stderr, "other\n"); break;
+ case GL_FRAMEBUFFER_UNSUPPORTED: mbgl::Log::Error(mbgl::Event::OpenGL, "unsupported\n"); break;
+ default: mbgl::Log::Error(mbgl::Event::OpenGL, "other\n"); break;
}
return;
}
@@ -93,7 +125,7 @@ void PrerenderedTexture::bindFramebuffer() {
}
void PrerenderedTexture::unbindFramebuffer() {
- glBindFramebuffer(GL_FRAMEBUFFER, previous_fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
if (fbo != 0) {
glDeleteFramebuffers(1, &fbo);
@@ -102,12 +134,12 @@ void PrerenderedTexture::unbindFramebuffer() {
}
void PrerenderedTexture::blur(Painter& painter, uint16_t passes) {
- const GLuint original_texture = texture;
+ const GLuint originalTexture = texture;
// Create a secondary texture
- GLuint secondary_texture;
- glGenTextures(1, &secondary_texture);
- glBindTexture(GL_TEXTURE_2D, secondary_texture);
+ GLuint secondaryTexture;
+ glGenTextures(1, &secondaryTexture);
+ glBindTexture(GL_TEXTURE_2D, secondaryTexture);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
@@ -123,32 +155,32 @@ void PrerenderedTexture::blur(Painter& painter, uint16_t passes) {
for (int i = 0; i < passes; i++) {
// Render horizontal
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, secondary_texture, 0);
-#if GL_EXT_discard_framebuffer
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, secondaryTexture, 0);
+#if GL_EXT_discard_framebuffer && !__ANDROID__
const GLenum discards[] = { GL_COLOR_ATTACHMENT0 };
glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards);
#endif
glClear(GL_COLOR_BUFFER_BIT);
painter.gaussianShader->u_offset = {{ 1.0f / float(properties.size), 0 }};
- glBindTexture(GL_TEXTURE_2D, original_texture);
+ glBindTexture(GL_TEXTURE_2D, originalTexture);
painter.coveringGaussianArray.bind(*painter.gaussianShader, painter.tileStencilBuffer, BUFFER_OFFSET(0));
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)painter.tileStencilBuffer.index());
// Render vertical
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, original_texture, 0);
-#if GL_EXT_discard_framebuffer
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, originalTexture, 0);
+#if GL_EXT_discard_framebuffer && !__ANDROID__
glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards);
#endif
glClear(GL_COLOR_BUFFER_BIT);
painter.gaussianShader->u_offset = {{ 0, 1.0f / float(properties.size) }};
- glBindTexture(GL_TEXTURE_2D, secondary_texture);
+ glBindTexture(GL_TEXTURE_2D, secondaryTexture);
painter.coveringGaussianArray.bind(*painter.gaussianShader, painter.tileStencilBuffer, BUFFER_OFFSET(0));
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)painter.tileStencilBuffer.index());
}
- glDeleteTextures(1, &secondary_texture);
+ glDeleteTextures(1, &secondaryTexture);
}
diff --git a/src/mbgl/renderer/prerendered_texture.hpp b/src/mbgl/renderer/prerendered_texture.hpp
index e4dc610418..3ccd24038d 100644
--- a/src/mbgl/renderer/prerendered_texture.hpp
+++ b/src/mbgl/renderer/prerendered_texture.hpp
@@ -26,10 +26,11 @@ public:
const StyleBucketRaster &properties;
private:
- GLint previous_fbo = 0;
+ GLint previousFbo = 0;
GLuint fbo = 0;
GLuint texture = 0;
- GLuint fbo_depth_stencil = 0;
+ GLuint fboDepth= 0;
+ GLuint fboStencil = 0;
};
}
diff --git a/src/mbgl/storage/asset_request.cpp b/src/mbgl/storage/asset_request.cpp
new file mode 100644
index 0000000000..e4684b10f2
--- /dev/null
+++ b/src/mbgl/storage/asset_request.cpp
@@ -0,0 +1,37 @@
+#include <mbgl/storage/asset_request.hpp>
+#include <mbgl/storage/asset_request_baton.hpp>
+#include <mbgl/storage/response.hpp>
+
+#include <uv.h>
+
+#include <cassert>
+
+#include <unistd.h>
+
+namespace mbgl {
+
+AssetRequest::AssetRequest(const std::string &path_, uv_loop_t *loop)
+ : BaseRequest(path_), ptr(new AssetRequestBaton(this, path, loop)) {
+}
+
+void AssetRequest::cancel() {
+ assert(std::this_thread::get_id() == threadId);
+
+ if (ptr) {
+ ptr->cancel();
+
+ // When deleting a AssetRequest object with a uv_fs_* call is in progress, we are making sure
+ // that the callback doesn't accidentally reference this object again.
+ ptr->request = nullptr;
+ ptr = nullptr;
+ }
+
+ notify();
+}
+
+AssetRequest::~AssetRequest() {
+ assert(std::this_thread::get_id() == threadId);
+ cancel();
+}
+
+}
diff --git a/src/mbgl/storage/asset_request.hpp b/src/mbgl/storage/asset_request.hpp
new file mode 100644
index 0000000000..3114d41ad2
--- /dev/null
+++ b/src/mbgl/storage/asset_request.hpp
@@ -0,0 +1,27 @@
+#ifndef MBGL_STORAGE_ASSET_REQUEST
+#define MBGL_STORAGE_ASSET_REQUEST
+
+#include <mbgl/storage/base_request.hpp>
+
+namespace mbgl {
+
+typedef struct uv_loop_s uv_loop_t;
+
+struct AssetRequestBaton;
+
+class AssetRequest : public BaseRequest {
+public:
+ AssetRequest(const std::string &path, uv_loop_t *loop);
+ ~AssetRequest();
+
+ void cancel();
+
+private:
+ AssetRequestBaton *ptr = nullptr;
+
+ friend struct AssetRequestBaton;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/storage/asset_request_baton.cpp b/src/mbgl/storage/asset_request_baton.cpp
new file mode 100644
index 0000000000..85f3a6ff2c
--- /dev/null
+++ b/src/mbgl/storage/asset_request_baton.cpp
@@ -0,0 +1,60 @@
+#include <mbgl/storage/asset_request_baton.hpp>
+#include <mbgl/storage/asset_request.hpp>
+#include <mbgl/storage/response.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/uv_detail.hpp>
+
+namespace mbgl {
+
+AssetRequestBaton::AssetRequestBaton(AssetRequest *request_, const std::string &path_, uv_loop_t *loop)
+ : threadId(std::this_thread::get_id()),
+ request(request_),
+ path(path_) {
+
+ asyncRun = mbgl::util::make_unique<uv::async>(loop, [this]() {
+ run(this);
+ });
+
+ asyncRun->send();
+}
+
+void AssetRequestBaton::cancel() {
+ canceled = true;
+}
+
+void AssetRequestBaton::notifyError(AssetRequestBaton *ptr, const int code, const char *message) {
+ assert(std::this_thread::get_id() == ptr->threadId);
+
+ if (ptr->request && !ptr->canceled) {
+ ptr->request->response = std::unique_ptr<Response>(new Response);
+ ptr->request->response->code = code;
+ ptr->request->response->message = message;
+ ptr->request->notify();
+ }
+}
+
+void AssetRequestBaton::notifySuccess(AssetRequestBaton *ptr, const std::string body) {
+assert(std::this_thread::get_id() == ptr->threadId);
+
+ if (ptr->request && !ptr->canceled) {
+ ptr->request->response = std::unique_ptr<Response>(new Response);
+ ptr->request->response->code = 200;
+ ptr->request->response->data = body;
+ ptr->request->notify();
+ }
+}
+
+void AssetRequestBaton::cleanup(AssetRequestBaton *ptr) {
+ assert(std::this_thread::get_id() == ptr->threadId);
+
+ if (ptr->request) {
+ ptr->request->ptr = nullptr;
+ }
+
+ ptr->asyncRun.reset();
+
+ delete ptr;
+ ptr = nullptr;
+}
+
+}
diff --git a/src/mbgl/storage/base_request.cpp b/src/mbgl/storage/base_request.cpp
index 5ce206996c..510bd7bf1c 100644
--- a/src/mbgl/storage/base_request.cpp
+++ b/src/mbgl/storage/base_request.cpp
@@ -19,13 +19,13 @@ void invoke(const std::forward_list<std::unique_ptr<Callback>> &list, Args&& ...
}
}
-BaseRequest::BaseRequest(const std::string &path_) : thread_id(std::this_thread::get_id()), path(path_) {
+BaseRequest::BaseRequest(const std::string &path_) : threadId(std::this_thread::get_id()), path(path_) {
}
// A base request can only be "canceled" by destroying the object. In that case, we'll have to
// notify all cancel callbacks.
BaseRequest::~BaseRequest() {
- assert(thread_id == std::this_thread::get_id());
+ assert(std::this_thread::get_id() == threadId);
notify();
}
@@ -34,7 +34,7 @@ void BaseRequest::retryImmediately() {
}
void BaseRequest::notify() {
- assert(thread_id == std::this_thread::get_id());
+ assert(std::this_thread::get_id() == threadId);
// The parameter exists solely so that any calls to ->remove()
// are not going to cause deallocation of this object while this call is in progress.
@@ -55,7 +55,7 @@ void BaseRequest::notify() {
}
Callback *BaseRequest::add(Callback &&callback, const util::ptr<BaseRequest> &request) {
- assert(thread_id == std::this_thread::get_id());
+ assert(std::this_thread::get_id() == threadId);
assert(this == request.get());
if (response) {
@@ -75,7 +75,7 @@ Callback *BaseRequest::add(Callback &&callback, const util::ptr<BaseRequest> &re
}
void BaseRequest::remove(Callback *callback) {
- assert(thread_id == std::this_thread::get_id());
+ assert(std::this_thread::get_id() == threadId);
callbacks.remove_if([=](const std::unique_ptr<Callback> &cb) {
return cb.get() == callback;
});
diff --git a/src/mbgl/storage/base_request.hpp b/src/mbgl/storage/base_request.hpp
index 2913c5eae5..5119c343e9 100644
--- a/src/mbgl/storage/base_request.hpp
+++ b/src/mbgl/storage/base_request.hpp
@@ -46,7 +46,7 @@ public:
virtual void retryImmediately();
public:
- const std::thread::id thread_id;
+ const std::thread::id threadId;
const std::string path;
std::unique_ptr<Response> response;
diff --git a/src/mbgl/storage/caching_http_file_source.cpp b/src/mbgl/storage/caching_http_file_source.cpp
index d676357f8d..4f32df3111 100644
--- a/src/mbgl/storage/caching_http_file_source.cpp
+++ b/src/mbgl/storage/caching_http_file_source.cpp
@@ -2,6 +2,7 @@
#include <mbgl/storage/file_request.hpp>
#include <mbgl/storage/http_request.hpp>
#include <mbgl/storage/sqlite_store.hpp>
+#include <mbgl/storage/asset_request.hpp>
#include <mbgl/util/uv-messenger.h>
#include <mbgl/util/mapbox.hpp>
#include <mbgl/util/std.hpp>
@@ -17,7 +18,7 @@ CachingHTTPFileSource::~CachingHTTPFileSource() {
}
void CachingHTTPFileSource::setLoop(uv_loop_t* loop_) {
- thread_id = std::this_thread::get_id();
+ threadId = std::this_thread::get_id();
store = !path.empty() ? util::ptr<SQLiteStore>(new SQLiteStore(loop_, path)) : nullptr;
loop = loop_;
queue = new uv_messenger_t;
@@ -34,7 +35,7 @@ bool CachingHTTPFileSource::hasLoop() {
}
void CachingHTTPFileSource::clearLoop() {
- assert(thread_id == std::this_thread::get_id());
+ assert(std::this_thread::get_id() == threadId);
assert(loop);
uv_messenger_stop(queue, [](uv_messenger_t *msgr) {
@@ -63,8 +64,12 @@ void CachingHTTPFileSource::setAccessToken(std::string value) {
accessToken.swap(value);
}
+std::string CachingHTTPFileSource::getAccessToken() const {
+ return accessToken;
+}
+
std::unique_ptr<Request> CachingHTTPFileSource::request(ResourceType type, const std::string& url_) {
- assert(thread_id == std::this_thread::get_id());
+ assert(std::this_thread::get_id() == threadId);
std::string url = url_;
@@ -93,6 +98,8 @@ std::unique_ptr<Request> CachingHTTPFileSource::request(ResourceType type, const
if (!req) {
if (url.substr(0, 7) == "file://") {
req = std::make_shared<FileRequest>(url.substr(7), loop);
+ } else if (url.substr(0, 8) == "asset://") {
+ req = std::make_shared<AssetRequest>(url.substr(8), loop);
} else {
req = std::make_shared<HTTPRequest>(type, url, loop, store);
}
@@ -104,7 +111,7 @@ std::unique_ptr<Request> CachingHTTPFileSource::request(ResourceType type, const
}
void CachingHTTPFileSource::prepare(std::function<void()> fn) {
- if (thread_id == std::this_thread::get_id()) {
+ if (std::this_thread::get_id() == threadId) {
fn();
} else {
uv_messenger_send(queue, new std::function<void()>(std::move(fn)));
diff --git a/src/mbgl/storage/file_request.cpp b/src/mbgl/storage/file_request.cpp
index 9f74c7b414..6cb882101d 100644
--- a/src/mbgl/storage/file_request.cpp
+++ b/src/mbgl/storage/file_request.cpp
@@ -15,7 +15,7 @@ FileRequest::FileRequest(const std::string &path_, uv_loop_t *loop)
}
void FileRequest::cancel() {
- assert(thread_id == std::this_thread::get_id());
+ assert(std::this_thread::get_id() == threadId);
if (ptr) {
ptr->cancel();
@@ -30,7 +30,7 @@ void FileRequest::cancel() {
}
FileRequest::~FileRequest() {
- assert(thread_id == std::this_thread::get_id());
+ assert(std::this_thread::get_id() == threadId);
cancel();
}
diff --git a/src/mbgl/storage/file_request.hpp b/src/mbgl/storage/file_request.hpp
index 2f883728ff..3de2d5b60d 100644
--- a/src/mbgl/storage/file_request.hpp
+++ b/src/mbgl/storage/file_request.hpp
@@ -24,4 +24,4 @@ private:
}
-#endif \ No newline at end of file
+#endif
diff --git a/src/mbgl/storage/file_request_baton.cpp b/src/mbgl/storage/file_request_baton.cpp
index 0a9c5f6f55..6da10552b2 100644
--- a/src/mbgl/storage/file_request_baton.cpp
+++ b/src/mbgl/storage/file_request_baton.cpp
@@ -8,9 +8,9 @@
namespace mbgl {
FileRequestBaton::FileRequestBaton(FileRequest *request_, const std::string &path, uv_loop_t *loop)
- : thread_id(std::this_thread::get_id()), request(request_) {
+ : threadId(std::this_thread::get_id()), request(request_) {
req.data = this;
- uv_fs_open(loop, &req, path.c_str(), O_RDONLY, S_IRUSR, file_opened);
+ uv_fs_open(loop, &req, path.c_str(), O_RDONLY, S_IRUSR, fileOpened);
}
FileRequestBaton::~FileRequestBaton() {
@@ -25,9 +25,9 @@ void FileRequestBaton::cancel() {
uv_cancel((uv_req_t *)&req);
}
-void FileRequestBaton::notify_error(uv_fs_t *req) {
- FileRequestBaton *ptr = (FileRequestBaton *)req->data;
- assert(ptr->thread_id == std::this_thread::get_id());
+void FileRequestBaton::notifyError(uv_fs_t *req) {
+ FileRequestBaton *ptr = reinterpret_cast<FileRequestBaton *>(req->data);
+ assert(std::this_thread::get_id() == ptr->threadId);
if (ptr->request && req->result < 0 && !ptr->canceled && req->result != UV_ECANCELED) {
ptr->request->response = util::make_unique<Response>();
@@ -41,13 +41,13 @@ void FileRequestBaton::notify_error(uv_fs_t *req) {
}
}
-void FileRequestBaton::file_opened(uv_fs_t *req) {
- FileRequestBaton *ptr = (FileRequestBaton *)req->data;
- assert(ptr->thread_id == std::this_thread::get_id());
+void FileRequestBaton::fileOpened(uv_fs_t *req) {
+ FileRequestBaton *ptr = reinterpret_cast<FileRequestBaton *>(req->data);
+ assert(std::this_thread::get_id() == ptr->threadId);
if (req->result < 0) {
// Opening failed or was canceled. There isn't much left we can do.
- notify_error(req);
+ notifyError(req);
cleanup(req);
} else {
const uv_file fd = uv_file(req->result);
@@ -58,25 +58,25 @@ void FileRequestBaton::file_opened(uv_fs_t *req) {
if (ptr->canceled || !ptr->request) {
// Either the FileRequest object has been destructed, or the
// request was canceled.
- uv_fs_close(req->loop, req, fd, file_closed);
+ uv_fs_close(req->loop, req, fd, fileClosed);
} else {
ptr->fd = fd;
- uv_fs_fstat(req->loop, req, fd, file_stated);
+ uv_fs_fstat(req->loop, req, fd, fileStated);
}
}
}
-void FileRequestBaton::file_stated(uv_fs_t *req) {
- FileRequestBaton *ptr = (FileRequestBaton *)req->data;
- assert(ptr->thread_id == std::this_thread::get_id());
+void FileRequestBaton::fileStated(uv_fs_t *req) {
+ FileRequestBaton *ptr = reinterpret_cast<FileRequestBaton *>(req->data);
+ assert(std::this_thread::get_id() == ptr->threadId);
if (req->result != 0 || ptr->canceled || !ptr->request) {
// Stating failed or was canceled. We already have an open file handle
// though, which we'll have to close.
- notify_error(req);
+ notifyError(req);
uv_fs_req_cleanup(req);
- uv_fs_close(req->loop, req, ptr->fd, file_closed);
+ uv_fs_close(req->loop, req, ptr->fd, fileClosed);
} else {
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
const uv_statbuf_t *stat = static_cast<const uv_statbuf_t *>(req->ptr);
@@ -98,29 +98,29 @@ void FileRequestBaton::file_stated(uv_fs_t *req) {
}
uv_fs_req_cleanup(req);
- uv_fs_close(req->loop, req, ptr->fd, file_closed);
+ uv_fs_close(req->loop, req, ptr->fd, fileClosed);
} else {
const unsigned int size = (unsigned int)(stat->st_size);
ptr->body.resize(size);
ptr->buffer = uv_buf_init(const_cast<char *>(ptr->body.data()), size);
uv_fs_req_cleanup(req);
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- uv_fs_read(req->loop, req, ptr->fd, ptr->buffer.base, ptr->buffer.len, -1, file_read);
+ uv_fs_read(req->loop, req, ptr->fd, ptr->buffer.base, ptr->buffer.len, -1, fileRead);
#else
- uv_fs_read(req->loop, req, ptr->fd, &ptr->buffer, 1, 0, file_read);
+ uv_fs_read(req->loop, req, ptr->fd, &ptr->buffer, 1, 0, fileRead);
#endif
}
}
}
-void FileRequestBaton::file_read(uv_fs_t *req) {
- FileRequestBaton *ptr = (FileRequestBaton *)req->data;
- assert(ptr->thread_id == std::this_thread::get_id());
+void FileRequestBaton::fileRead(uv_fs_t *req) {
+ FileRequestBaton *ptr = reinterpret_cast<FileRequestBaton *>(req->data);
+ assert(std::this_thread::get_id() == ptr->threadId);
if (req->result < 0 || ptr->canceled || !ptr->request) {
- // Stating failed or was canceled. We already have an open file handle
+ // Reading failed or was canceled. We already have an open file handle
// though, which we'll have to close.
- notify_error(req);
+ notifyError(req);
} else {
// File was successfully read.
if (ptr->request) {
@@ -132,11 +132,11 @@ void FileRequestBaton::file_read(uv_fs_t *req) {
}
uv_fs_req_cleanup(req);
- uv_fs_close(req->loop, req, ptr->fd, file_closed);
+ uv_fs_close(req->loop, req, ptr->fd, fileClosed);
}
-void FileRequestBaton::file_closed(uv_fs_t *req) {
- assert(((FileRequestBaton *)req->data)->thread_id == std::this_thread::get_id());
+void FileRequestBaton::fileClosed(uv_fs_t *req) {
+ assert(std::this_thread::get_id() == reinterpret_cast<FileRequestBaton *>(req->data)->threadId);
if (req->result < 0) {
// Closing the file failed. But there isn't anything we can do.
@@ -146,8 +146,8 @@ void FileRequestBaton::file_closed(uv_fs_t *req) {
}
void FileRequestBaton::cleanup(uv_fs_t *req) {
- FileRequestBaton *ptr = (FileRequestBaton *)req->data;
- assert(ptr->thread_id == std::this_thread::get_id());
+ FileRequestBaton *ptr = reinterpret_cast<FileRequestBaton *>(req->data);
+ assert(std::this_thread::get_id() == ptr->threadId);
if (ptr->request) {
ptr->request->ptr = nullptr;
@@ -155,6 +155,7 @@ void FileRequestBaton::cleanup(uv_fs_t *req) {
uv_fs_req_cleanup(req);
delete ptr;
+ ptr = nullptr;
}
}
diff --git a/src/mbgl/storage/file_request_baton.hpp b/src/mbgl/storage/file_request_baton.hpp
index 897c88061d..ef9c170fe7 100644
--- a/src/mbgl/storage/file_request_baton.hpp
+++ b/src/mbgl/storage/file_request_baton.hpp
@@ -13,14 +13,14 @@ struct FileRequestBaton {
~FileRequestBaton();
void cancel();
- static void file_opened(uv_fs_t *req);
- static void file_stated(uv_fs_t *req);
- static void file_read(uv_fs_t *req);
- static void file_closed(uv_fs_t *req);
- static void notify_error(uv_fs_t *req);
+ static void fileOpened(uv_fs_t *req);
+ static void fileStated(uv_fs_t *req);
+ static void fileRead(uv_fs_t *req);
+ static void fileClosed(uv_fs_t *req);
+ static void notifyError(uv_fs_t *req);
static void cleanup(uv_fs_t *req);
- const std::thread::id thread_id;
+ const std::thread::id threadId;
FileRequest *request = nullptr;
uv_fs_t req;
uv_file fd = -1;
diff --git a/src/mbgl/storage/http_request.cpp b/src/mbgl/storage/http_request.cpp
index ebb9a84823..57e6c260ef 100644
--- a/src/mbgl/storage/http_request.cpp
+++ b/src/mbgl/storage/http_request.cpp
@@ -1,6 +1,7 @@
#include <mbgl/storage/http_request.hpp>
#include <mbgl/storage/sqlite_store.hpp>
#include <mbgl/storage/http_request_baton.hpp>
+#include <mbgl/platform/log.hpp>
#include <uv.h>
@@ -21,7 +22,7 @@ struct CacheRequestBaton {
};
HTTPRequest::HTTPRequest(ResourceType type_, const std::string &path_, uv_loop_t *loop_, util::ptr<SQLiteStore> store_)
- : BaseRequest(path_), thread_id(std::this_thread::get_id()), loop(loop_), store(store_), type(type_) {
+ : BaseRequest(path_), threadId(std::this_thread::get_id()), loop(loop_), store(store_), type(type_) {
if (store) {
startCacheRequest();
} else {
@@ -30,24 +31,24 @@ HTTPRequest::HTTPRequest(ResourceType type_, const std::string &path_, uv_loop_t
}
void HTTPRequest::startCacheRequest() {
- assert(std::this_thread::get_id() == thread_id);
+ assert(std::this_thread::get_id() == threadId);
- cache_baton = new CacheRequestBaton;
- cache_baton->request = this;
- cache_baton->path = path;
- cache_baton->store = store;
+ cacheBaton = new CacheRequestBaton;
+ cacheBaton->request = this;
+ cacheBaton->path = path;
+ cacheBaton->store = store;
store->get(path, [](std::unique_ptr<Response> &&response_, void *ptr) {
// Wrap in a unique_ptr, so it'll always get auto-destructed.
std::unique_ptr<CacheRequestBaton> baton((CacheRequestBaton *)ptr);
if (baton->request) {
- baton->request->cache_baton = nullptr;
+ baton->request->cacheBaton = nullptr;
baton->request->handleCacheResponse(std::move(response_));
}
- }, cache_baton);
+ }, cacheBaton);
}
void HTTPRequest::handleCacheResponse(std::unique_ptr<Response> &&res) {
- assert(std::this_thread::get_id() == thread_id);
+ assert(std::this_thread::get_id() == threadId);
if (res) {
// This entry was stored in the cache. Now determine if we need to revalidate.
@@ -68,25 +69,25 @@ void HTTPRequest::handleCacheResponse(std::unique_ptr<Response> &&res) {
}
void HTTPRequest::startHTTPRequest(std::unique_ptr<Response> &&res) {
- assert(std::this_thread::get_id() == thread_id);
- assert(!http_baton);
+ assert(std::this_thread::get_id() == threadId);
+ assert(!httpBaton);
- http_baton = std::make_shared<HTTPRequestBaton>(path);
- http_baton->request = this;
- http_baton->async = new uv_async_t;
- http_baton->response = std::move(res);
- http_baton->async->data = new util::ptr<HTTPRequestBaton>(http_baton);
+ httpBaton = std::make_shared<HTTPRequestBaton>(path);
+ httpBaton->request = this;
+ httpBaton->async = new uv_async_t;
+ httpBaton->response = std::move(res);
+ httpBaton->async->data = new util::ptr<HTTPRequestBaton>(httpBaton);
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- uv_async_init(loop, http_baton->async, [](uv_async_t *async, int) {
+ uv_async_init(loop, httpBaton->async, [](uv_async_t *async, int) {
#else
- uv_async_init(loop, http_baton->async, [](uv_async_t *async) {
+ uv_async_init(loop, httpBaton->async, [](uv_async_t *async) {
#endif
util::ptr<HTTPRequestBaton> &baton = *(util::ptr<HTTPRequestBaton> *)async->data;
if (baton->request) {
HTTPRequest *request = baton->request;
- request->http_baton.reset();
+ request->httpBaton.reset();
baton->request = nullptr;
request->handleHTTPResponse(baton->type, std::move(baton->response));
}
@@ -98,14 +99,14 @@ void HTTPRequest::startHTTPRequest(std::unique_ptr<Response> &&res) {
});
});
attempts++;
- HTTPRequestBaton::start(http_baton);
+ HTTPRequestBaton::start(httpBaton);
}
void HTTPRequest::handleHTTPResponse(HTTPResponseType responseType, std::unique_ptr<Response> &&res) {
- assert(std::this_thread::get_id() == thread_id);
- assert(!http_baton);
+ assert(std::this_thread::get_id() == threadId);
+ assert(!httpBaton);
assert(!response);
switch (responseType) {
@@ -192,76 +193,76 @@ void HTTPRequest::handleHTTPResponse(HTTPResponseType responseType, std::unique_
using RetryBaton = std::pair<HTTPRequest *, std::unique_ptr<Response>>;
void HTTPRequest::retryHTTPRequest(std::unique_ptr<Response> &&res, uint64_t timeout) {
- assert(std::this_thread::get_id() == thread_id);
- assert(!backoff_timer);
- backoff_timer = new uv_timer_t();
- uv_timer_init(loop, backoff_timer);
- backoff_timer->data = new RetryBaton(this, std::move(res));
+ assert(std::this_thread::get_id() == threadId);
+ assert(!backoffTimer);
+ backoffTimer = new uv_timer_t();
+ uv_timer_init(loop, backoffTimer);
+ backoffTimer->data = new RetryBaton(this, std::move(res));
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- uv_timer_start(backoff_timer, [](uv_timer_t *timer, int) {
+ uv_timer_start(backoffTimer, [](uv_timer_t *timer, int) {
#else
- uv_timer_start(backoff_timer, [](uv_timer_t *timer) {
+ uv_timer_start(backoffTimer, [](uv_timer_t *timer) {
#endif
std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(timer->data) };
pair->first->startHTTPRequest(std::move(pair->second));
- pair->first->backoff_timer = nullptr;
+ pair->first->backoffTimer = nullptr;
uv_timer_stop(timer);
uv_close((uv_handle_t *)timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; });
}, timeout, 0);
}
void HTTPRequest::removeHTTPBaton() {
- assert(std::this_thread::get_id() == thread_id);
- if (http_baton) {
- http_baton->request = nullptr;
- HTTPRequestBaton::stop(http_baton);
- http_baton.reset();
+ assert(std::this_thread::get_id() == threadId);
+ if (httpBaton) {
+ httpBaton->request = nullptr;
+ HTTPRequestBaton::stop(httpBaton);
+ httpBaton.reset();
}
}
void HTTPRequest::removeCacheBaton() {
- assert(std::this_thread::get_id() == thread_id);
- if (cache_baton) {
+ assert(std::this_thread::get_id() == threadId);
+ if (cacheBaton) {
// Make sre that this object doesn't accidentally get accessed when it is destructed before
// the callback returned. They are being run in the same thread, so just setting it to
// null is sufficient.
// Note: We don't manually delete the CacheRequestBaton since it'll be deleted by the
// callback.
- cache_baton->request = nullptr;
- cache_baton = nullptr;
+ cacheBaton->request = nullptr;
+ cacheBaton = nullptr;
}
}
void HTTPRequest::removeBackoffTimer() {
- assert(std::this_thread::get_id() == thread_id);
- if (backoff_timer) {
- delete static_cast<RetryBaton *>(backoff_timer->data);
- uv_timer_stop(backoff_timer);
- uv_close((uv_handle_t *)backoff_timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; });
- backoff_timer = nullptr;
+ assert(std::this_thread::get_id() == threadId);
+ if (backoffTimer) {
+ delete static_cast<RetryBaton *>(backoffTimer->data);
+ uv_timer_stop(backoffTimer);
+ uv_close((uv_handle_t *)backoffTimer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; });
+ backoffTimer = nullptr;
}
}
void HTTPRequest::retryImmediately() {
- assert(std::this_thread::get_id() == thread_id);
- if (!cache_baton && !http_baton) {
- if (backoff_timer) {
+ assert(std::this_thread::get_id() == threadId);
+ if (!cacheBaton && !httpBaton) {
+ if (backoffTimer) {
// Retry immediately.
- uv_timer_stop(backoff_timer);
- std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(backoff_timer->data) };
+ uv_timer_stop(backoffTimer);
+ std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(backoffTimer->data) };
assert(pair->first == this);
startHTTPRequest(std::move(pair->second));
- uv_close((uv_handle_t *)backoff_timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; });
- backoff_timer = nullptr;
+ uv_close((uv_handle_t *)backoffTimer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; });
+ backoffTimer = nullptr;
} else {
- assert(!"We should always have a backoff_timer when there are no batons");
+ assert(!"We should always have a backoffTimer when there are no batons");
}
}
}
void HTTPRequest::cancel() {
- assert(std::this_thread::get_id() == thread_id);
+ assert(std::this_thread::get_id() == threadId);
removeCacheBaton();
removeHTTPBaton();
removeBackoffTimer();
@@ -270,7 +271,7 @@ void HTTPRequest::cancel() {
HTTPRequest::~HTTPRequest() {
- assert(std::this_thread::get_id() == thread_id);
+ assert(std::this_thread::get_id() == threadId);
cancel();
}
diff --git a/src/mbgl/storage/http_request.hpp b/src/mbgl/storage/http_request.hpp
index 71d6e8814c..7cc72101d5 100644
--- a/src/mbgl/storage/http_request.hpp
+++ b/src/mbgl/storage/http_request.hpp
@@ -41,11 +41,11 @@ private:
void removeBackoffTimer();
private:
- const std::thread::id thread_id;
+ const std::thread::id threadId;
uv_loop_t *const loop;
- CacheRequestBaton *cache_baton = nullptr;
- util::ptr<HTTPRequestBaton> http_baton;
- uv_timer_t *backoff_timer = nullptr;
+ CacheRequestBaton *cacheBaton = nullptr;
+ util::ptr<HTTPRequestBaton> httpBaton;
+ uv_timer_t *backoffTimer = nullptr;
util::ptr<SQLiteStore> store;
const ResourceType type;
uint8_t attempts = 0;
@@ -55,4 +55,4 @@ private:
}
-#endif \ No newline at end of file
+#endif
diff --git a/src/mbgl/storage/http_request_baton.cpp b/src/mbgl/storage/http_request_baton.cpp
index 315708f4e0..d781a3bdf4 100644
--- a/src/mbgl/storage/http_request_baton.cpp
+++ b/src/mbgl/storage/http_request_baton.cpp
@@ -3,7 +3,7 @@
namespace mbgl {
-HTTPRequestBaton::HTTPRequestBaton(const std::string &path_) : thread_id(std::this_thread::get_id()), path(path_) {
+HTTPRequestBaton::HTTPRequestBaton(const std::string &path_) : threadId(std::this_thread::get_id()), path(path_) {
}
HTTPRequestBaton::~HTTPRequestBaton() {
diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp
index 15ca4e14fb..9669e0e4a7 100644
--- a/src/mbgl/style/style.cpp
+++ b/src/mbgl/style/style.cpp
@@ -8,6 +8,7 @@
#include <mbgl/util/error.hpp>
#include <mbgl/util/std.hpp>
#include <mbgl/util/uv_detail.hpp>
+#include <mbgl/platform/log.hpp>
#include <csscolorparser/csscolorparser.hpp>
#include <rapidjson/document.h>
@@ -91,6 +92,7 @@ void Style::loadJSON(const uint8_t *const data) {
rapidjson::Document doc;
doc.Parse<0>((const char *const)data);
if (doc.HasParseError()) {
+ Log::Error(Event::ParseStyle, "Error parsing style JSON at %i: %s", doc.GetErrorOffset(), doc.GetParseError());
throw error::style_parse(doc.GetErrorOffset(), doc.GetParseError());
}
diff --git a/update_icons.sh b/update_icons.sh
new file mode 100755
index 0000000000..7940aacc28
--- /dev/null
+++ b/update_icons.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
+
+if [[ -z `which iconoblast` ]]; then
+ echo "Requires https://github.com/mapbox/iconoblast!"
+ exit 1
+fi
+
+commit=`git rev-parse --short HEAD`
+branch=`git rev-parse --abbrev-ref HEAD`
+repo=`git remote show origin | grep 'Fetch URL' | sed -e 's/.*github\.com:mapbox\///' -e 's/\.git$//' -e 's/^mapbox-//'`
+
+pwd=`pwd`
+cd android/java/app/src/main/res
+for folder in `find . -type d -name drawable-\* -maxdepth 1`
+do
+ cd ${folder}
+ cp icon.png icon_burned.png
+ iconoblast icon_burned.png $commit $branch $repo
+ cd ..
+done
+cd ${pwd}