Merge pull request #676 from mapbox/android-mason

Android GL mega-merge
This commit is contained in:
Leith Bade
2015-01-27 07:23:43 +11:00
125 changed files with 8406 additions and 185 deletions

2
.gitignore vendored
View File

@@ -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
*~

2
.mason

Submodule .mason updated: 462fe28479...a3ebabd0c6

View File

@@ -4,6 +4,12 @@ matrix:
exclude:
- os: linux
include:
- os: linux
env: CONFIG=release MASON_PLATFORM=android TESTMUNK=yes
compiler: clang
- os: linux
env: CONFIG=debug MASON_PLATFORM=android TESTMUNK=no
compiler: clang
- os: linux
env: BUILDTYPE=Release JOBS=16
compiler: clang
@@ -19,21 +25,26 @@ matrix:
env:
global:
- secure: "bG4YYWMfl9API0MSRgmOaJrlGLv06tRg9KJNawBieZvBJbITPpxVGJZT3/l/SEJ+Rl15e2dRex4k+MGQlmT2SqPQxTEYWv1qxNigKPPcla7IWeNmWWqW8uVvFjdglojgBOK2k/xErVQtA4zDfi3mwSXH4DKwquXWsoEKmX2SV7M="
- secure: "Cbvap9ubVKgjPe3hUhI6JGeDZzBXHpOG9RaYKh+SdoIPhKnlJiNOYm1egomi+e4uqJInlFKuVHTw7Ng9Cun6Zm0jIxpkSchv1GpsR7hmB3UGnGed19Dw8121FwuUaktN+4YnbVlsyd+u8EHD3+h58t4eELrLrZolM4rS7DL6caA="
- secure: "MZHblLZXG/jWf2w0ZFlxCLDwx2qtGgRDODQyg1BR7JIuMz6AtWv8XR/sUczWLbiABCL0a/NzJF1g4v2pI7X69IntcjOdIABBgTh7++6+1TJ0Kp8viEltb55nQG3lHy/R6fOaI7Pj9tuCX0PCRtGA5C/fGnodLGEjy3RVOJ09ln0="
- secure: "KaSQbhgjtV7ZCkesHmvrNsbQVjk5SPfGKB1VkWenRGYhLF45HpSRNwSxMQddZ566Pg7qIFgF1iWl/B0QW3B6AWL5WmzQ5AOJgwS876pNIc/UT7ubMPtgAtjpvw1bQvQP3B8MrB+3OE5c6tD+a3LhR9krV//dOsfErR5Yy+3Mbkc="
- ANDROID_BUILD_INSTANCE_PROFILE: arn:aws:iam::234858372212:instance-profile/android-gl-build/travis/instance-profile/travis-mapbox-gl-native-AndroidInstanceProfile-13EA815DD8IQ9
- secure: "RiBIBfVhhaMjU5ksuwJO3shdvG9FpinBjdSv4co9jg9171SR8edNriedHjVKSIeBhSGNmZmX+twS3dJS/By6tl/LKh9sTynA+ZAYYljkE7jn881B/gMrlYvdAA6og5KvkhV1/0iJWlhuZrMTkhpDR200iLgg3EWBhWjltzmDW/I="
- secure: "CHBiUM60TolDbQnn+4IRA/tvOKwKs3g9EDvv8YHSJMg3FuHmjKQkprBasvxf3hnTXg4WLZEubmeDcyJ6RRzPP5mMSr/hksYl0pSjj/6TUecE5fHPVVeN7txVqkpOBf9i45Y+iBUQMjBb1NnDK3pHXxpnAs1Q/pe7vReErj4GF1U="
- LD_LIBRARY_PATH: '/usr/local/lib'
- TERM: dumb
before_install:
- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then sudo service mysql stop; fi
- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then sudo service postgresql stop; fi
- source ./scripts/local_mason.sh
- source ./scripts/travis_helper.sh
- source ./scripts/flags.sh
- ./scripts/travis_before_install.sh
- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then export LD_LIBRARY_PATH=`mason prefix mesa 10.3.1`/lib; fi
- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then glxinfo; fi
- if [[ ${TRAVIS_OS_NAME} == "linux" && ${MASON_PLATFORM} != "android" ]]; then export LD_LIBRARY_PATH=`mason prefix mesa 10.3.1`/lib; fi
- if [[ ${TRAVIS_OS_NAME} == "linux" && ${MASON_PLATFORM} != "android" ]]; then glxinfo; fi
install:
- make config.gypi
- if [[ ${MASON_PLATFORM} != "android" ]]; then make config.gypi; fi
- ulimit -c
before_script:

View File

@@ -2,6 +2,7 @@ BUILDTYPE ?= Release
PYTHON ?= python
V ?= 1
PREFIX ?= /usr/local
JOBS ?= 1
ifeq ($(shell uname -s), Darwin)
PLATFORM ?= osx
@@ -74,9 +75,34 @@ build/linux/mapboxgl-app.xcodeproj: linux/mapboxgl-app.gyp config.gypi
deps/run_gyp linux/mapboxgl-app.gyp -Iconfig.gypi -Dplatform=linux --depth=. --generator-output=./build -f xcode
.PHONY: build/bin/render.xcodeproj
build/bin/render.xcodeproj: bin/render.gyp config.gypi
build/bin/render.xcodeproj: bin/render.gyp config.gypi
deps/run_gyp bin/render.gyp -Iconfig.gypi -Dplatform=$(PLATFORM) --depth=. --generator-output=./build -f xcode
.PHONY: android
android:
./scripts/local_mason.sh && \
MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env PATH && \
export CXX="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env CXX`" && \
export CC="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env CC`" && \
export LD="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env LD`" && \
export LINK="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env CXX`" && \
export AR="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env AR`" && \
export RANLIB="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env RANLIB`" && \
export LDFLAGS="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env LDFLAGS` ${LDFLAGS}" && \
export CFLAGS="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env CFLAGS` ${CFLAGS}" && \
export CPPFLAGS="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env CPPFLAGS` ${CPPFLAGS}" && \
export PATH="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env PATH`:${PATH}" && \
MASON_PLATFORM=android ./configure config-android.gypi && \
deps/run_gyp android/mapboxgl-app.gyp -Iconfig-android.gypi -Dplatform=android --depth=. --generator-output=./build/android -f make-android && \
$(MAKE) -C build/android -j$(JOBS) BUILDTYPE=$(BUILDTYPE) V=$(V) androidapp && \
mkdir -p android/java/lib/src/main/jniLibs/armeabi-v7a && \
cp build/android/out/$(BUILDTYPE)/lib.target/libmapbox-gl.so android/java/lib/src/main/jniLibs/armeabi-v7a/libmapbox-gl.so && \
mkdir -p android/java/lib/src/main/assets && \
cp build/android/out/$(BUILDTYPE)/ca-bundle.crt android/java/lib/src/main/assets/ca-bundle.crt && \
cp -r build/android/out/$(BUILDTYPE)/styles android/java/lib/src/main/assets/styles && \
cd android/java && \
./gradlew --parallel-threads=$(JOBS) build
##### Test cases ###############################################################
test: build/test/Makefile
@@ -157,7 +183,10 @@ clean: clear_sqlite_cache clear_xcode_cache
-find ./deps/gyp -name "*.pyc" -exec rm {} \;
-rm -rf ./build/
-rm -rf ./macosx/build/
-rm -rf ./config.gypi ./config-ios.gypi
-rm -rf ./config.gypi ./config-ios.gypi ./config-android.gypi
-rm -rf ./android/java/build ./android/java/app/build ./android/java/lib/build
-rm -rf ./android/java/lib/src/main/jniLibs ./android/java/lib/src/main/assets
-rm -f ./android/test/features.zip
distclean: clean
-rm -rf ./mason_packages

View File

@@ -1,7 +1,7 @@
[![Travis](https://api.travis-ci.org/mapbox/mapbox-gl-native.svg?branch=master)](https://travis-ci.org/mapbox/mapbox-gl-native/builds)
An OpenGL renderer for [Mapbox Vector Tiles](https://www.mapbox.com/blog/vector-tiles),
implemented in C++11, currently targeting iOS, OS X, and Ubuntu Linux.
implemented in C++11, currently targeting iOS, OS X, Android, and Ubuntu Linux.
# Depends
@@ -62,9 +62,9 @@ We are using Ubuntu for development. While the software should work on other dis
Install GCC 4.8+ if you are running Ubuntu 13.10 or older. Alternatively, you can also use Clang 3.4+.
sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt-get install gcc-4.8 g++-4.8
sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt-get install gcc-4.8 g++-4.8
Ensure you have git and other build essentials:
@@ -81,17 +81,17 @@ Install glfw3 dependencies:
Finally, install Boost. If you're running Ubuntu 12.04 or older, you need to install a backport PPA since the version provided by APT doesn't contain Boost Geometry:
sudo add-apt-repository --yes ppa:boost-latest/ppa
sudo apt-get update
sudo apt-get install libboost1.55-dev
sudo add-apt-repository --yes ppa:boost-latest/ppa
sudo apt-get update
sudo apt-get install libboost1.55-dev
Otherwise, you can just install
sudo apt-get install libboost-dev
sudo apt-get install libboost-dev
Once you're done installing the build dependencies, you can get started by running
./configure
./configure
Then, you can then proceed to build the library:
@@ -102,6 +102,48 @@ Set an access token as described below, and then run:
make run-linux
## Android (on Linux)
Install a few build depedencies:
apt-get install -y make git build-essential automake \
libtool make cmake pkg-config lib32stdc++6 lib32z1
Install Oracle JDK 7 (requires license agreement) from http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
export JAVA_HOME="/dir/to/jdk1.7.0_71"
Install the [Android NDK Revision 10d](https://developer.android.com/tools/sdk/ndk/index.html).
export ANDROID_NDK_PATH="/dir/to/android-ndk-r10d"
Install the Android SDK. We recommend doing this by way of [Android Studio](https://developer.android.com/sdk/installing/studio.html).
export ANDROID_HOME="/dir/to/android-sdk-linux"
Run:
make android
You can then open `android/java` in Android Studio via "Import Non-Android Studio Project".
## Android (on OS X)
Install Oracle JDK 7 (requires license agreement) from http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
Install the [Android NDK Revision 10d](https://developer.android.com/tools/sdk/ndk/index.html) for 64-bit OS X.
export ANDROID_NDK_PATH="/dir/to/android-ndk-r10d"
Install the Android SDK. We recommend doing this by way of [Android Studio](https://developer.android.com/sdk/installing/studio.html). The latest versions recommend that you place the SDK someplace like `/usr/local/android-sdk-macosx`.
export ANDROID_HOME="/dir/to/android-sdk-macosx"
Run:
make android
You can then open `android/java` in Android Studio via "Import Non-Android Studio Project".
# Troubleshooting
@@ -123,6 +165,8 @@ For iOS and OS X use of the demo apps in Xcode, setup the access token by editin
For Linux, set the environment variable `MAPBOX_ACCESS_TOKEN` to your token.
For Android, gradle will take the value of `MAPBOX_ACCESS_TOKEN` and save it to `android/java/app/src/main/res/raw/token.txt` where the app will read it from.
# Style
Some styles in JSON format are included at `./styles`. See the [style spec](https://github.com/mapbox/mapbox-gl-style-spec) for more details.

202
android/LICENSE_COMMONS.txt Normal file
View File

@@ -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.

View File

@@ -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.

13
android/LICENSE_LOST.txt Normal file
View File

@@ -0,0 +1,13 @@
Copyright (c) 2014 Mapzen
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

1024
android/cpp/jni.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,826 @@
#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());
throw new std::runtime_error("eglQueryString() failed");
} else {
char buf[513];
for (int len = std::strlen(str), pos = 0; len > 0; len -= 512, pos += 512) {
strncpy(buf, str + pos, 512);
buf[512] = 0;
mbgl::Log::Info(mbgl::Event::OpenGL, "EGL %s: %s", label, buf);
}
}
}
void log_gl_string(GLenum name, const char *label) {
const GLubyte *str = glGetString(name);
if (str == nullptr) {
mbgl::Log::Error(mbgl::Event::OpenGL, "glGetString(%d) returned error %d", name,
glGetError());
throw new std::runtime_error("glGetString() failed");
} else {
char buf[513];
for (int len = std::strlen(reinterpret_cast<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::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
throw new std::runtime_error("GetEnv() failed");
}
obj = nullptr;
vm = nullptr;
}
void NativeMapView::activate() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::activate");
if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE) && (context != EGL_NO_CONTEXT)) {
if (!eglMakeCurrent(display, surface, surface, context)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent() returned error %d",
eglGetError());
throw new std::runtime_error("eglMakeCurrent() failed");
}
} else {
mbgl::Log::Info(mbgl::Event::Android, "Not activating as we are not ready");
}
}
void NativeMapView::deactivate() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::deactivate");
if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d",
eglGetError());
throw new std::runtime_error("eglMakeCurrent() failed");
}
}
void NativeMapView::swap() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::swap");
if (map.needsSwap() && (display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE)) {
if (!eglSwapBuffers(display, surface)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglSwapBuffers() returned error %d",
eglGetError());
throw new std::runtime_error("eglSwapBuffers() failed");
}
map.swapped();
updateFps();
} else {
mbgl::Log::Info(mbgl::Event::Android, "Not swapping as we are not ready");
}
}
void NativeMapView::notify() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::notify()");
// noop
}
mbgl::Map &NativeMapView::getMap() { return map; }
mbgl::CachingHTTPFileSource &NativeMapView::getFileSource() { return fileSource; }
bool NativeMapView::inEmulator() {
// Detect if we are in emulator
char prop[PROP_VALUE_MAX];
__system_property_get("ro.kernel.qemu", prop);
return strtol(prop, nullptr, 0) == 1;
}
void NativeMapView::initializeDisplay() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::initializeDisplay");
assert(display == EGL_NO_DISPLAY);
assert(config == nullptr);
assert(format < 0);
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetDisplay() returned error %d", eglGetError());
throw new std::runtime_error("eglGetDisplay() failed");
}
EGLint major, minor;
if (!eglInitialize(display, &major, &minor)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglInitialize() returned error %d", eglGetError());
throw new std::runtime_error("eglInitialize() failed");
}
if ((major <= 1) && (minor < 3)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "EGL version is too low, need 1.3, got %d.%d", major,
minor);
throw new std::runtime_error("EGL version is too low");
}
log_egl_string(display, EGL_VENDOR, "Vendor");
log_egl_string(display, EGL_VERSION, "Version");
log_egl_string(display, EGL_CLIENT_APIS, "Client APIs");
log_egl_string(display, EGL_EXTENSIONS, "Client Extensions");
// Detect if we are in emulator
if (inEmulator()) {
mbgl::Log::Warning(mbgl::Event::Android, "In emulator! Enabling hacks :-(");
}
// Get all configs at least RGB 565 with 16 depth and 8 stencil
EGLint configAttribs[] = {
EGL_CONFIG_CAVEAT, EGL_NONE, EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BUFFER_SIZE, 16, EGL_RED_SIZE,
5, EGL_GREEN_SIZE, 6,
EGL_BLUE_SIZE, 5, EGL_DEPTH_SIZE,
16, EGL_STENCIL_SIZE, 8,
(inEmulator() ? EGL_NONE : EGL_CONFORMANT), EGL_OPENGL_ES2_BIT, // Ugly hack
(inEmulator() ? EGL_NONE : EGL_COLOR_BUFFER_TYPE), EGL_RGB_BUFFER, // Ugly hack
EGL_NONE};
EGLint numConfigs;
if (!eglChooseConfig(display, configAttribs, nullptr, 0, &numConfigs)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig(NULL) returned error %d",
eglGetError());
throw new std::runtime_error("eglChooseConfig() failed");
}
if (numConfigs < 1) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig() returned no configs.");
throw new std::runtime_error("eglChooseConfig() failed");
}
const std::unique_ptr<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());
throw new std::runtime_error("eglChooseConfig() failed");
}
config = chooseConfig(configs.get(), numConfigs);
if (config == nullptr) {
mbgl::Log::Error(mbgl::Event::OpenGL, "No config chosen");
throw new std::runtime_error("No config chosen");
}
if (!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib() returned error %d",
eglGetError());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
mbgl::Log::Info(mbgl::Event::OpenGL, "Chosen window format is %d", format);
}
void NativeMapView::terminateDisplay() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::terminateDisplay");
if (display != EGL_NO_DISPLAY) {
// Destroy the surface first, if it still exists. This call needs a valid surface.
if (surface != EGL_NO_SURFACE) {
if (!eglDestroySurface(display, surface)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroySurface() returned error %d",
eglGetError());
throw new std::runtime_error("eglDestroySurface() failed");
}
surface = EGL_NO_SURFACE;
}
if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError());
throw new std::runtime_error("eglMakeCurrent() failed");
}
if (!eglTerminate(display)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglTerminate() returned error %d",
eglGetError());
throw new std::runtime_error("eglTerminate() failed");
}
}
display = EGL_NO_DISPLAY;
config = nullptr;
format = -1;
}
void NativeMapView::initializeContext() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::initializeContext");
assert(display != EGL_NO_DISPLAY);
assert(context == EGL_NO_CONTEXT);
assert(config != nullptr);
const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
if (context == EGL_NO_CONTEXT) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglCreateContext() returned error %d",
eglGetError());
throw new std::runtime_error("eglCreateContext() failed");
}
}
void NativeMapView::terminateContext() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::terminateContext");
if (display != EGL_NO_DISPLAY) {
if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError());
throw new std::runtime_error("eglMakeCurrent() failed");
}
if (context != EGL_NO_CONTEXT) {
if (!eglDestroyContext(display, context)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroyContext() returned error %d",
eglGetError());
throw new std::runtime_error("eglDestroyContext() failed");
}
}
}
context = EGL_NO_CONTEXT;
}
void NativeMapView::createSurface(ANativeWindow *window_) {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::createSurface");
assert(window == nullptr);
assert(window_ != nullptr);
window = window_;
assert(display != EGL_NO_DISPLAY);
assert(surface == EGL_NO_SURFACE);
assert(config != nullptr);
assert(format >= 0);
ANativeWindow_setBuffersGeometry(window, 0, 0, format);
const EGLint surfaceAttribs[] = {EGL_NONE};
surface = eglCreateWindowSurface(display, config, window, surfaceAttribs);
if (surface == EGL_NO_SURFACE) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglCreateWindowSurface() returned error %d",
eglGetError());
throw new std::runtime_error("eglCreateWindowSurface() failed");
}
if (!firstTime) {
firstTime = true;
if (!eglMakeCurrent(display, surface, surface, context)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent() returned error %d",
eglGetError());
throw new std::runtime_error("eglMakeCurrent() failed");
}
log_gl_string(GL_VENDOR, "Vendor");
log_gl_string(GL_RENDERER, "Renderer");
log_gl_string(GL_VERSION, "Version");
if (!inEmulator()) {
log_gl_string(GL_SHADING_LANGUAGE_VERSION,
"SL Version"); // In the emulator this returns NULL with error code 0?
// https://code.google.com/p/android/issues/detail?id=78977
}
log_gl_string(GL_EXTENSIONS, "Extensions");
loadExtensions();
if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError());
throw new std::runtime_error("eglMakeCurrent() failed");
}
}
resume();
}
void NativeMapView::destroySurface() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::destroySurface");
pause(true);
if (surface != EGL_NO_SURFACE) {
if (!eglDestroySurface(display, surface)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroySurface() returned error %d",
eglGetError());
throw new std::runtime_error("eglDestroySurface() failed");
}
}
surface = EGL_NO_SURFACE;
if (window != nullptr) {
ANativeWindow_release(window);
window = nullptr;
}
}
// Speed
/*
typedef enum {
Format16Bit = 0,
Format32BitNoAlpha = 1,
Format32BitAlpha = 2,
Format24Bit = 3,
Unknown = 4
} BufferFormat;
typedef enum {
Format16Depth8Stencil = 0,
Format24Depth8Stencil = 1,
} DepthStencilFormat;
*/
// Quality
typedef enum {
Format16Bit = 3,
Format32BitNoAlpha = 1,
Format32BitAlpha = 2,
Format24Bit = 0,
Unknown = 4
} BufferFormat;
typedef enum {
Format16Depth8Stencil = 1,
Format24Depth8Stencil = 0,
} DepthStencilFormat;
// Tuple is <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());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
if (!eglGetConfigAttrib(display, configs[i], EGL_CONFORMANT, &conformant)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglGetConfigAttrib(EGL_CONFORMANT) returned error %d", eglGetError());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
if (!eglGetConfigAttrib(display, configs[i], EGL_BUFFER_SIZE, &bits)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglGetConfigAttrib(EGL_BUFFER_SIZE) returned error %d",
eglGetError());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
if (!eglGetConfigAttrib(display, configs[i], EGL_RED_SIZE, &red)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglGetConfigAttrib(EGL_RED_SIZE) returned error %d", eglGetError());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
if (!eglGetConfigAttrib(display, configs[i], EGL_GREEN_SIZE, &green)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglGetConfigAttrib(EGL_GREEN_SIZE) returned error %d", eglGetError());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
if (!eglGetConfigAttrib(display, configs[i], EGL_BLUE_SIZE, &blue)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglGetConfigAttrib(EGL_BLUE_SIZE) returned error %d", eglGetError());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
if (!eglGetConfigAttrib(display, configs[i], EGL_ALPHA_SIZE, &alpha)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglGetConfigAttrib(EGL_ALPHA_SIZE) returned error %d", eglGetError());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
if (!eglGetConfigAttrib(display, configs[i], EGL_ALPHA_MASK_SIZE, &alphaMask)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglGetConfigAttrib(EGL_ALPHA_MASK_SIZE) returned error %d",
eglGetError());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
if (!eglGetConfigAttrib(display, configs[i], EGL_DEPTH_SIZE, &depth)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglGetConfigAttrib(EGL_DEPTH_SIZE) returned error %d", eglGetError());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
if (!eglGetConfigAttrib(display, configs[i], EGL_STENCIL_SIZE, &stencil)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglGetConfigAttrib(EGL_STENCIL_SIZE) returned error %d",
eglGetError());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
if (!eglGetConfigAttrib(display, configs[i], EGL_SAMPLE_BUFFERS, &sampleBuffers)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglGetConfigAttrib(EGL_SAMPLE_BUFFERS) returned error %d",
eglGetError());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
if (!eglGetConfigAttrib(display, configs[i], EGL_SAMPLES, &samples)) {
mbgl::Log::Error(mbgl::Event::OpenGL,
"eglGetConfigAttrib(EGL_SAMPLES) returned error %d", eglGetError());
throw new std::runtime_error("eglGetConfigAttrib() failed");
}
mbgl::Log::Info(mbgl::Event::OpenGL, "...Caveat: %d", caveat);
mbgl::Log::Info(mbgl::Event::OpenGL, "...Conformant: %d", conformant);
mbgl::Log::Info(mbgl::Event::OpenGL, "...Color: %d", bits);
mbgl::Log::Info(mbgl::Event::OpenGL, "...Red: %d", red);
mbgl::Log::Info(mbgl::Event::OpenGL, "...Green: %d", green);
mbgl::Log::Info(mbgl::Event::OpenGL, "...Blue: %d", blue);
mbgl::Log::Info(mbgl::Event::OpenGL, "...Alpha: %d", alpha);
mbgl::Log::Info(mbgl::Event::OpenGL, "...Alpha mask: %d", alphaMask);
mbgl::Log::Info(mbgl::Event::OpenGL, "...Depth: %d", depth);
mbgl::Log::Info(mbgl::Event::OpenGL, "...Stencil: %d", stencil);
mbgl::Log::Info(mbgl::Event::OpenGL, "...Sample buffers: %d", sampleBuffers);
mbgl::Log::Info(mbgl::Event::OpenGL, "...Samples: %d", samples);
bool configOk = true;
configOk &= (depth == 24) || (depth == 16);
configOk &= stencil == 8;
configOk &= sampleBuffers == 0;
configOk &= samples == 0;
// Filter our configs first for depth, stencil and anti-aliasing
if (configOk) {
// Work out the config's buffer format
BufferFormat bufferFormat;
if ((bits == 16) && (red == 5) && (green == 6) && (blue == 5) && (alpha == 0)) {
bufferFormat = Format16Bit;
} else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) {
bufferFormat = Format32BitNoAlpha;
} else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 8)) {
bufferFormat = Format32BitAlpha;
} else if ((bits == 24) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) {
bufferFormat = Format24Bit;
} else {
bufferFormat = Unknown;
}
// Work out the config's depth stencil format
DepthStencilFormat depthStencilFormat;
if ((depth == 16) && (stencil == 8)) {
depthStencilFormat = Format16Depth8Stencil;
} else {
depthStencilFormat = Format24Depth8Stencil;
}
bool isNotConformant = (conformant & EGL_OPENGL_ES2_BIT) != EGL_OPENGL_ES2_BIT;
bool isCaveat = caveat != EGL_NONE;
EGLConfig configId = configs[i];
// Ignore formats we don't recognise
if (bufferFormat != Unknown) {
configList.push_back(std::make_tuple(bufferFormat, depthStencilFormat,
isNotConformant, isCaveat, i, configId));
}
}
}
if (configList.empty()) {
mbgl::Log::Error(mbgl::Event::OpenGL, "Config list was empty.");
}
// Sort the configs to find the best one
configList.sort();
usingDepth24 = std::get<1>(configList.front()) == Format24Depth8Stencil;
bool isConformant = !std::get<2>(configList.front());
bool isCaveat = std::get<3>(configList.front());
int configNum = std::get<4>(configList.front());
EGLConfig configId = std::get<5>(configList.front());
mbgl::Log::Info(mbgl::Event::OpenGL, "Chosen config is %d", configNum);
if (isCaveat) {
mbgl::Log::Warning(mbgl::Event::OpenGL, "Chosen config has a caveat.");
}
if (!isConformant) {
mbgl::Log::Warning(mbgl::Event::OpenGL, "Chosen config is not conformant.");
}
return configId;
}
void NativeMapView::start() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::start");
if (display == EGL_NO_DISPLAY) {
initializeDisplay();
}
if (context == EGL_NO_CONTEXT) {
initializeContext();
}
assert(display != EGL_NO_DISPLAY);
assert(context != EGL_NO_CONTEXT);
map.start(true);
}
void NativeMapView::loadExtensions() {
const GLubyte *str = glGetString(GL_EXTENSIONS);
if (str == nullptr) {
mbgl::Log::Error(mbgl::Event::OpenGL, "glGetString(GL_EXTENSIONS) returned error %d",
glGetError());
return;
}
std::string extensions(reinterpret_cast<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 = reinterpret_cast<gl::PFNGLBINDVERTEXARRAYPROC>(
eglGetProcAddress("glBindVertexArrayOES"));
gl::DeleteVertexArrays = reinterpret_cast<gl::PFNGLDELETEVERTEXARRAYSPROC>(
eglGetProcAddress("glDeleteVertexArraysOES"));
gl::GenVertexArrays = reinterpret_cast<gl::PFNGLGENVERTEXARRAYSPROC>(
eglGetProcAddress("glGenVertexArraysOES"));
gl::IsVertexArray =
reinterpret_cast<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.");
}
}
if (extensions.find("GL_KHR_debug") != std::string::npos) {
mbgl::Log::Info(mbgl::Event::OpenGL, "Using GL_KHR_debug.");
gl::DebugMessageControl = reinterpret_cast<gl::PFNGLDEBUGMESSAGECONTROLPROC>(
eglGetProcAddress("glDebugMessageControl"));
gl::DebugMessageInsert = reinterpret_cast<gl::PFNGLDEBUGMESSAGEINSERTPROC>(
eglGetProcAddress("glDebugMessageInsert"));
gl::DebugMessageCallback = reinterpret_cast<gl::PFNGLDEBUGMESSAGECALLBACKPROC>(
eglGetProcAddress("glDebugMessageCallback"));
gl::GetDebugMessageLog = reinterpret_cast<gl::PFNGLGETDEBUGMESSAGELOGPROC>(
eglGetProcAddress("glGetDebugMessageLog"));
gl::GetPointerv =
reinterpret_cast<gl::PFNGLGETPOINTERVPROC>(eglGetProcAddress("glGetPointerv"));
gl::PushDebugGroup =
reinterpret_cast<gl::PFNGLPUSHDEBUGGROUPPROC>(eglGetProcAddress("glPushDebugGroup"));
gl::PopDebugGroup =
reinterpret_cast<gl::PFNGLPOPDEBUGGROUPPROC>(eglGetProcAddress("glPopDebugGroup"));
gl::ObjectLabel =
reinterpret_cast<gl::PFNGLOBJECTLABELPROC>(eglGetProcAddress("glObjectLabel"));
gl::GetObjectLabel =
reinterpret_cast<gl::PFNGLGETOBJECTLABELPROC>(eglGetProcAddress("glGetObjectLabel"));
gl::ObjectPtrLabel =
reinterpret_cast<gl::PFNGLOBJECTPTRLABELPROC>(eglGetProcAddress("glObjectPtrLabel"));
gl::GetObjectPtrLabel = reinterpret_cast<gl::PFNGLGETOBJECTPTRLABELPROC>(
eglGetProcAddress("glGetObjectPtrLabel"));
assert(gl::DebugMessageControl != nullptr);
assert(gl::DebugMessageInsert != nullptr);
assert(gl::DebugMessageCallback != nullptr);
assert(gl::GetDebugMessageLog != nullptr);
assert(gl::GetPointerv != nullptr);
assert(gl::PushDebugGroup != nullptr);
assert(gl::PopDebugGroup != nullptr);
assert(gl::ObjectLabel != nullptr);
assert(gl::GetObjectLabel != nullptr);
assert(gl::ObjectPtrLabel != nullptr);
assert(gl::GetObjectPtrLabel != nullptr);
} else {
if (extensions.find("GL_EXT_debug_marker") != std::string::npos) {
mbgl::Log::Info(mbgl::Event::OpenGL, "Using GL_EXT_debug_marker.");
gl::InsertEventMarkerEXT = reinterpret_cast<gl::PFNGLINSERTEVENTMARKEREXTPROC>(
eglGetProcAddress("glInsertEventMarkerEXT"));
gl::PushGroupMarkerEXT = reinterpret_cast<gl::PFNGLPUSHGROUPMARKEREXTPROC>(
eglGetProcAddress("glPushGroupMarkerEXT"));
gl::PopGroupMarkerEXT = reinterpret_cast<gl::PFNGLPOPGROUPMARKEREXTPROC>(
eglGetProcAddress("glPopGroupMarkerEXT"));
assert(gl::InsertEventMarkerEXT != nullptr);
assert(gl::PushGroupMarkerEXT != nullptr);
assert(gl::PopGroupMarkerEXT != nullptr);
}
if (extensions.find("GL_EXT_debug_label") != std::string::npos) {
mbgl::Log::Info(mbgl::Event::OpenGL, "Using GL_EXT_debug_label.");
gl::LabelObjectEXT = reinterpret_cast<gl::PFNGLLABELOBJECTEXTPROC>(
eglGetProcAddress("glLabelObjectEXT"));
gl::GetObjectLabelEXT = reinterpret_cast<gl::PFNGLGETOBJECTLABELEXTPROC>(
eglGetProcAddress("glGetObjectLabelEXT"));
assert(gl::LabelObjectEXT != nullptr);
assert(gl::GetObjectLabelEXT != nullptr);
}
}
}
void NativeMapView::stop() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::stop");
if ((display != EGL_NO_DISPLAY) && (context != EGL_NO_CONTEXT)) {
map.stop();
}
}
void NativeMapView::pause(bool waitForPause) {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::pause %s",
(waitForPause) ? "true" : "false");
if ((display != EGL_NO_DISPLAY) && (context != EGL_NO_CONTEXT)) {
map.pause(waitForPause);
}
}
void NativeMapView::resume() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::resume");
assert(display != EGL_NO_DISPLAY);
assert(context != EGL_NO_CONTEXT);
if (surface != EGL_NO_SURFACE) {
map.resume();
} else {
mbgl::Log::Debug(mbgl::Event::Android, "Not resuming because we are not ready");
}
}
void NativeMapView::notifyMapChange(mbgl::MapChange, mbgl::timestamp) {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::notifyMapChange()");
assert(vm != nullptr);
assert(obj != nullptr);
JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::notifyMapChange()", NULL};
jint ret;
JNIEnv *env = nullptr;
bool detach = false;
ret = vm->GetEnv(reinterpret_cast<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);
throw new std::runtime_error("GetEnv() failed");
} else {
ret = vm->AttachCurrentThread(&env, &args);
if (ret != JNI_OK) {
mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret);
throw new std::runtime_error("AttachCurrentThread() failed");
}
detach = true;
}
}
env->CallVoidMethod(obj, onMapChangedId);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
}
if (detach) {
if ((ret = vm->DetachCurrentThread()) != JNI_OK) {
mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret);
throw new std::runtime_error("DetachCurrentThread() failed");
}
}
env = nullptr;
}
void NativeMapView::enableFps(bool enable) {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::enableFps()");
fpsEnabled = enable;
}
void NativeMapView::updateFps() {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::updateFps()");
if (!fpsEnabled) {
return;
}
static int frames = 0;
static int64_t timeElapsed = 0LL;
frames++;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
int64_t currentTime = now.tv_sec * 1000000000LL + now.tv_nsec;
if (currentTime - timeElapsed >= 1) {
fps = frames / ((currentTime - timeElapsed) / 1E9);
mbgl::Log::Debug(mbgl::Event::Render, "FPS: %4.2f", fps);
timeElapsed = currentTime;
frames = 0;
}
assert(vm != nullptr);
assert(obj != nullptr);
JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::updateFps()", NULL};
jint ret;
JNIEnv *env = nullptr;
bool detach = false;
ret = vm->GetEnv(reinterpret_cast<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);
throw new std::runtime_error("GetEnv() failed");
} else {
ret = vm->AttachCurrentThread(&env, &args);
if (ret != JNI_OK) {
mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret);
throw new std::runtime_error("AttachCurrentThread() failed");
}
detach = true;
}
}
env->CallVoidMethod(obj, onFpsChangedId, fps);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
}
if (detach) {
if ((ret = vm->DetachCurrentThread()) != JNI_OK) {
mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret);
throw new std::runtime_error("DetachCurrentThread() failed");
}
}
env = nullptr;
}
}
}

21
android/java/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# Gradle files
.gradle/
# IntelliJ files
.idea
*.iml
# Build files
build/
*/build/
*.so
*.apk
# Lib assets
lib/src/main/assets/
# Local settings
local.properties
# Token file
app/src/main/res/raw/token.txt

View File

@@ -0,0 +1,63 @@
apply plugin: 'com.android.application'
task accessToken {
def rawDir = new File("app/src/main/res/raw")
rawDir.mkdirs()
def tokenFile = new File("app/src/main/res/raw/token.txt")
String token = System.getenv('MAPBOX_ACCESS_TOKEN')
if (token == null) {
throw new InvalidUserDataException("You must set the MAPBOX_ACCESS_TOKEN environment variable.")
}
tokenFile.write(token)
}
gradle.projectsEvaluated {
preBuild.dependsOn('accessToken')
}
android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
repositories {
jcenter()
}
defaultConfig {
applicationId "com.mapbox.mapboxgl.app"
minSdkVersion 14
targetSdkVersion 21
versionCode 3
versionName "0.1.2"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
lintOptions {
checkAllWarnings true
disable 'UnusedAttribute'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
dependencies {
compile project(':lib')
compile 'com.android.support:support-annotations:21.0.0'
compile 'com.android.support:support-v4:21.0.3'
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.mapzen.android:lost:0.2'
}

34
android/java/app/lint.xml Normal file
View File

@@ -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>

View File

@@ -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"
android:supportsRtl="true">
<activity
android:name="com.mapbox.mapboxgl.app.MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,270 @@
package com.mapbox.mapboxgl.app;
import android.location.Location;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.mapbox.mapboxgl.lib.LonLatZoom;
import com.mapbox.mapboxgl.lib.MapView;
import com.mapzen.android.lost.LocationClient;
import com.mapzen.android.lost.LocationListener;
import com.mapzen.android.lost.LocationRequest;
public class MainActivity extends ActionBarActivity {
//
// Static members
//
// Tag used for logging
private static final String TAG = "MainActivity";
//
// Instance members
//
// Holds the MapFragment
private MapFragment mMapFragment;
// The FPS label
private TextView mFpsTextView;
// The compass
private ImageView mCompassView;
// Used for GPS
private boolean mIsGpsOn = false;
private LocationClient mLocationClient;
private LocationListener mLocationListener;
private LocationRequest mLocationRequest;
//
// Lifecycle events
//
// Called when activity is created
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v(TAG, "onCreate");
// Load the layout
setContentView(R.layout.activity_main);
mMapFragment = (MapFragment)getSupportFragmentManager().findFragmentById(R.id.fragment_map);
mMapFragment.getMap().setOnFpsChangedListener(new MyOnFpsChangedListener());
mMapFragment.getMap().setOnMapChangedListener(new MyOnMapChangedListener());
mFpsTextView = (TextView)findViewById(R.id.view_fps);
mFpsTextView.setText("");
mCompassView = (ImageView)findViewById(R.id.view_compass);
mCompassView.setOnClickListener(new CompassOnClickListener());
// Add a toolbar as the action bar
Toolbar mainToolbar = (Toolbar)findViewById(R.id.toolbar_main);
setSupportActionBar(mainToolbar);
getSupportActionBar().setDisplayShowTitleEnabled(false);
// Add the spinner to select map styles
Spinner styleSpinner = (Spinner)findViewById(R.id.spinner_style);
ArrayAdapter styleAdapter = ArrayAdapter.createFromResource(getSupportActionBar().getThemedContext(),
R.array.style_list, android.R.layout.simple_spinner_dropdown_item);
styleSpinner.setAdapter(styleAdapter);
styleSpinner.setOnItemSelectedListener(new StyleSpinnerListener());
// Prepare for GPS
mLocationRequest = LocationRequest.create();
mLocationRequest.setInterval(0);
mLocationRequest.setSmallestDisplacement(0);
mLocationClient = new LocationClient(getApplicationContext(), new LocationClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle connectionHint) {
updateLocation(mLocationClient.getLastLocation());
mLocationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
//Toast.makeText(getApplicationContext(), "Location: " + location.toString(), Toast.LENGTH_SHORT).show();
updateLocation(location);
}
};
mLocationClient.requestLocationUpdates(mLocationRequest, mLocationListener);
}
@Override
public void onDisconnected() {
mLocationListener = null;
}
});
}
// Called when our app goes into the background
@Override
public void onPause() {
super.onPause();
Log.v(TAG, "onPause");
// Cancel GPS
if (mIsGpsOn) {
mLocationClient.disconnect();
}
}
// Called when our app comes into the foreground
@Override
public void onResume() {
super.onResume();
Log.v(TAG, "onResume");
// Restart GPS
// Cancel any outstanding GPS
if (mIsGpsOn) {
mLocationClient.connect();
}
}
//
// Other events
//
// Adds items to the action bar menu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_main, menu);
return super.onCreateOptionsMenu(menu);
}
// Called when pressing action bar items
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_gps:
// Toggle GPS position updates
if (mIsGpsOn) {
mIsGpsOn = false;
item.setIcon(R.drawable.ic_action_location_searching);
mLocationClient.disconnect();
} else {
mIsGpsOn = true;
item.setIcon(R.drawable.ic_action_location_found);
mLocationClient.connect();
}
return true;
case R.id.action_debug:
// Toggle debug mode
mMapFragment.getMap().toggleDebug();
// Show the FPS counter
if (mMapFragment.getMap().isDebugActive()) {
mFpsTextView.setVisibility(View.VISIBLE);
mFpsTextView.setText(getResources().getString(R.string.label_fps));
} else {
mFpsTextView.setVisibility(View.INVISIBLE);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
// Handles location updates from GPS
private void updateLocation(Location location) {
if (location != null) {
LonLatZoom coordinate = new LonLatZoom(location.getLongitude(), location.getLatitude(), 16);
mMapFragment.getMap().setCenterCoordinate(coordinate, true);
}
}
// This class handles style change events
private class StyleSpinnerListener implements AdapterView.OnItemSelectedListener {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
switch(position) {
// Bright
case 0:
mMapFragment.getMap().setStyleUrl("asset://styles/styles/bright-v6.json");
break;
// Basic
case 1:
mMapFragment.getMap().setStyleUrl("asset://styles/styles/basic-v6.json");
break;
// Outdoors
case 2:
mMapFragment.getMap().setStyleUrl("asset://styles/styles/outdoors-v6.json");
break;
// Satellite
case 3:
mMapFragment.getMap().setStyleUrl("asset://styles/styles/satellite-v6.json");
break;
// Pencil
case 4:
mMapFragment.getMap().setStyleUrl("asset://styles/styles/pencil-v6.json");
break;
// Empty
case 5:
mMapFragment.getMap().setStyleUrl("asset://styles/styles/empty-v6.json");
break;
default:
onNothingSelected(parent);
break;
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
mMapFragment.getMap().setStyleUrl("");
}
}
// Called when FPS changes
public class MyOnFpsChangedListener implements MapView.OnFpsChangedListener {
@Override
public void onFpsChanged(double fps) {
mFpsTextView.setText(getResources().getString(R.string.label_fps) + String.format(" %4.2f", fps));
}
}
// Called when map state changes
public class MyOnMapChangedListener implements MapView.OnMapChangedListener {
@Override
public void onMapChanged() {
mCompassView.setRotation((float)mMapFragment.getMap().getDirection());
}
}
// Called when someone presses the compass
public class CompassOnClickListener implements View.OnClickListener {
@Override
public void onClick(View view) {
mMapFragment.getMap().resetNorth();
}
}
}

View File

@@ -0,0 +1,129 @@
package com.mapbox.mapboxgl.app;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.mapbox.mapboxgl.lib.MapView;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class MapFragment extends Fragment {
//
// Static members
//
// Tag used for logging
private static final String TAG = "MapFragment";
//
// Instance members
//
// The map
private MapView mMap;
//
// Lifecycle events
//
// Called when the fragment is created
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
Log.v(TAG, "onCreateView");
// Create the map
mMap = (MapView) inflater.inflate(R.layout.fragment_main, container, true);
// Load the access token
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(getActivity().getApplicationContext().getResources().openRawResource(R.raw.token)));
String line = reader.readLine();
mMap.setAccessToken(line);
} catch (IOException e) {
// Do nothing
}
// Need to pass on any saved state to the map
mMap.onCreate(savedInstanceState);
// Return the map as the root view
return mMap;
}
// Called when the fragment is destroyed
@Override
public void onDestroyView() {
super.onDestroyView();
Log.v(TAG, "onDestroyView");
// Need to pass on to view
mMap.onDestroy();
mMap = null;
}
// Called when the fragment is visible
@Override
public void onStart() {
super.onStart();
Log.v(TAG, "onStart");
// Need to pass on to view
mMap.onStart();
}
// Called when the fragment is invisible
@Override
public void onStop() {
super.onStop();
Log.v(TAG, "onStop");
// Need to pass on to view
mMap.onStop();
}
// Called when the fragment is in the background
@Override
public void onPause() {
super.onPause();
Log.v(TAG, "onPause");
// Need to pass on to view
mMap.onPause();
}
// Called when the fragment is no longer in the background
@Override
public void onResume() {
super.onResume();
Log.v(TAG, "onResume");
// Need to pass on to view
mMap.onResume();
}
// Called before fragment is destroyed
@Override
public void onSaveInstanceState(Bundle outState) {
Log.v(TAG, "onSaveInstanceState");
// Need to retrieve any saved state from the map
mMap.onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}
//
// Property methods
//
public MapView getMap() {
return mMap;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -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="48dp"
android:layout_height="48dp"
android:layout_gravity="end|top"
android:padding="10dp"
android:contentDescription="@string/compass_desc"
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>

View File

@@ -0,0 +1,4 @@
<?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:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -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_searching"
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>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Mapbox GL</string>
<string name="action_gps">Toggle GPS location</string>
<string name="action_debug">Toggle debug mode</string>
<string name="label_fps">FPS:</string>
<string name="compass_desc">Map compass. Click to reset the map rotation to North.</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>

View File

@@ -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>

View File

@@ -0,0 +1,9 @@
// 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'
}
}

Binary file not shown.

View File

@@ -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

164
android/java/gradlew vendored Executable file
View File

@@ -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 "$@"

90
android/java/gradlew.bat vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1,47 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
repositories {
jcenter()
}
defaultConfig {
minSdkVersion 14
targetSdkVersion 21
versionCode 3
versionName "0.1.2"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
lintOptions {
checkAllWarnings true
}
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.3'
compile 'com.android.support:support-v4:21.0.3'
}

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<lint>
</lint>

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,554 @@
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 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 addClass(String clazz) {
nativeAddClass(mNativeMapViewPtr, clazz);
}
public void removeClass(String clazz) {
nativeRemoveClass(mNativeMapViewPtr, clazz);
}
public boolean hasClass(String clazz) {
return nativeHasClass(mNativeMapViewPtr, clazz);
}
public void setClasses(List<String> classes) {
nativeSetClasses(mNativeMapViewPtr, classes);
}
public List<String> getClasses() {
return nativeGetClasses(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 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 nativeAddClass(long nativeMapViewPtr, String clazz);
private native void nativeRemoveClass(long nativeMapViewPtr, String clazz);
private native boolean nativeHasClass(long nativeMapViewPtr, String clazz);
private native void nativeSetClasses(long nativeMapViewPtr,
List<String> classes);
private native List<String> nativeGetClasses(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);
}

View File

@@ -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>

View File

@@ -0,0 +1,2 @@
include ':app'
include ':lib'

64
android/mapboxgl-app.gyp Normal file
View File

@@ -0,0 +1,64 @@
{
'includes': [
'../gyp/common.gypi',
],
'targets': [
{
'target_name': 'androidapp',
'product_name': 'mapbox-gl',
'type': 'shared_library',
'sources': [
'./cpp/native_map_view.cpp',
'./cpp/jni.cpp',
],
'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)'
}],
},
],
}

8
android/scripts/build-debug.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -e
set -o pipefail
export BUILDTYPE=Debug
./android/scripts/common.sh $1

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -e
set -o pipefail
export BUILDTYPE=Release
export TESTMUNK=yes
./android/scripts/common.sh $1

24
android/scripts/common.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -e
set -o pipefail
NAME=$1
export JOBS=2
export CC=clang CXX=clang++
export CXXFLAGS="-Wno-unknown-warning-option -Wno-unused-local-typedefs -Wno-unknown-pragmas ${CXXFLAGS}";
export MASON_PLATFORM=android
mkdir ./android/java/app/src/main/res/raw/
echo "${MAPBOX_ACCESS_TOKEN}" >> ./android/java/app/src/main/res/raw/token.txt
make android BUILDTYPE=$BUILDTYPE JOBS=$JOBS
aws s3 cp ./android/java/app/build/outputs/apk/app-debug.apk s3://mapbox-gl-testing/android/${NAME}/app-debug.apk
aws s3 cp ./android/java/app/build/outputs/apk/app-release-unsigned.apk s3://mapbox-gl-testing/android/${NAME}/app-release-unsigned.apk
if [[ $TESTMUNK == "yes" ]]; then
./android/scripts/upload-testmunk.sh ./android/java/app/build/outputs/apk/app-debug.apk
fi

82
android/scripts/run-build.sh Executable file
View File

@@ -0,0 +1,82 @@
#!/usr/bin/env bash
set -e
set -o pipefail
cwd=$(pwd)
region="us-east-1"
region_ami="ami-f0ed9098"
sleep=10
instance_name="android-gl-build-$TRAVIS_REPO_SLUG-$TRAVIS_JOB_NUMBER"
echo $ami_name
NAME=$TRAVIS_JOB_ID
user_data="#!/bin/bash
cd /android
git clone git://github.com/mapbox/mapbox-gl-native.git
pushd mapbox-gl-native
if [[ $TRAVIS_PULL_REQUEST == 'false' ]]; then
git checkout $TRAVIS_COMMIT
else
git fetch origin +refs/pull/$TRAVIS_PULL_REQUEST/merge:
git checkout FETCH_HEAD
fi
git submodule update --init --recursive
export ANDROID_NDK_PATH=/android/android-ndk-r10d
export JAVA_HOME=/android/jdk1.7.0_71
export ANDROID_HOME=/android/android-sdk-linux
export PATH=\$PATH:/android/jdk1.7.0_71/bin
export MAPBOX_ACCESS_TOKEN=$MAPBOX_ACCESS_TOKEN
export TESTMUNK_KEY=$TESTMUNK_KEY
if ./android/scripts/build-$CONFIG.sh $NAME &>../build.log; then
echo 'ANDROID BUILD PASSED'
else
echo 'ANDROID BUILD FAILED'
fi
popd
aws s3 cp --region us-east-1 build.log s3://mapbox-gl-testing/android/${NAME}/build-log.txt
shutdown -P now"
id=$(aws ec2 run-instances \
--region $region \
--image-id $region_ami \
--count 1 \
--instance-type m3.large \
--iam-instance-profile Arn="$ANDROID_BUILD_INSTANCE_PROFILE" \
--instance-initiated-shutdown-behavior terminate \
--user-data "$user_data" | jq -r '.Instances[0].InstanceId')
echo "Instance: $region $id"
sleep 10
result=$(aws ec2 create-tags --region $region --resources $id --tags "Key=Name,Value=$instance_name")
instance_status_terminated=$(aws ec2 describe-instances --region $region --instance-id $id | jq -r '.Reservations[0].Instances[0].State.Name')
until [ "$instance_status_terminated" = "terminated" ]; do
instance_status_terminated=$(aws ec2 describe-instances --region $region --instance-id $id | jq -r '.Reservations[0].Instances[0].State.Name')
echo "Instance terminating status $region $id: $instance_status_terminated"
sleep $sleep
done
echo "Build finished"
output=$(aws ec2 get-console-output --region $region --instance-id $id | jq -r '.Output')
aws s3 cp --region $region s3://mapbox-gl-testing/android/${NAME}/build-log.txt build.log
cat build.log
if [[ $output == *"ANDROID BUILD PASSED"* ]]; then
echo "ANDROID BUILD PASSED"
exit 0
else
echo "ANDROID BUILD FAILED"
exit 1
fi

View File

@@ -0,0 +1,25 @@
#!/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 android/test/features.zip android/test/features/
RESPONSE=$(curl -f -H "Accept: application/vnd.testmunk.v1+json" -F "file=@android/test/features.zip" "https://${TESTMUNK_KEY}@api.testmunk.com/apps/${APP_NAME}/testcases")
echo "uploading apk..."
RESPONSE=$(curl -f -H "Accept: application/vnd.testmunk.v1+json" -F "file=@${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}"

1
android/test/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
features.zip

View File

@@ -0,0 +1,5 @@
Feature: Testapp V.1.2
Scenario: 1) Start app and load map
Given I await for 5 seconds

View File

@@ -0,0 +1 @@
require 'calabash-android/calabash_steps'

View File

@@ -0,0 +1,6 @@
Then /^I await for ([\d\.]+) second(?:s)?$/ do |num_seconds|
num_seconds = num_seconds.to_f
sleep num_seconds
end

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1 @@
require 'calabash-android/cucumber'

View File

BIN
common/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

25
configure vendored
View File

@@ -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.1
LIBZIP_VERSION=0.11.2
;;
*)
GLFW_VERSION=e1ae9af5
SQLITE_VERSION=3.8.6
@@ -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+=" }
}

View File

@@ -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',
],
}],

83
gyp/mbgl-android.gypi Normal file
View File

@@ -0,0 +1,83 @@
{
'targets': [
{ 'target_name': 'mbgl-android',
'product_name': 'mbgl-android',
'type': 'static_library',
'standalone_static_library': 1,
'hard_dependency': 1,
'dependencies': [
'version',
],
'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/shader_cache_data.cpp',
'../platform/android/log_android.cpp',
'../platform/android/asset_request_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',
'../src',
],
'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)' ],
}]
],
},
},
],
}

View File

@@ -12,5 +12,9 @@
'includes': ['mbgl-linux.gypi'],
'variables': { 'platform_library': 'mbgl-linux' },
}],
['platform == "android"', {
'includes': ['mbgl-android.gypi'],
'variables': { 'platform_library': 'mbgl-android' },
}],
],
}

View File

@@ -0,0 +1,41 @@
#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 std::string androidRelease;
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

View File

@@ -0,0 +1,87 @@
#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 activate() override;
void deactivate() override;
void swap() override;
void notify() override;
void notifyMapChange(mbgl::MapChange change, mbgl::timestamp delay) override;
mbgl::Map &getMap();
mbgl::CachingHTTPFileSource &getFileSource();
void initializeDisplay();
void terminateDisplay();
void initializeContext();
void terminateContext();
void 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();
bool inEmulator();
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

View File

@@ -13,6 +13,8 @@
#include <iosfwd>
#include <set>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <functional>
namespace mbgl {
@@ -38,7 +40,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.
@@ -46,6 +48,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.
@@ -66,7 +74,7 @@ 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 addClass(const std::string&);
@@ -76,6 +84,7 @@ public:
std::vector<std::string> getClasses() 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;
@@ -126,6 +135,9 @@ private:
util::ptr<Sprite> getSprite();
uv::worker& getWorker();
// Checks if render thread needs to pause
void checkForPause();
// Setup
void setup();
@@ -154,6 +166,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;
@@ -195,6 +215,8 @@ private:
std::string styleJSON = "";
std::vector<std::string> classes;
std::atomic_uint_fast64_t defaultTransitionDuration;
bool debug = false;
timestamp animationTime = 0;

View File

@@ -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

View File

@@ -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" },
});

View File

@@ -18,7 +18,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
@@ -35,6 +36,13 @@ struct Error : ::std::runtime_error {
const GLenum code;
};
void checkError(const char *cmd, const char *file, int line);
#if defined(DEBUG)
#define MBGL_CHECK_ERROR(cmd) ([&]() { struct _ { inline ~_() { ::mbgl::gl::checkError(#cmd, __FILE__, __LINE__); } } _; return cmd; }())
#else
#define MBGL_CHECK_ERROR(cmd) (cmd)
#endif
// GL_KHR_debug / GL_ARB_debug_output
#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242
@@ -136,6 +144,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
@@ -153,9 +169,9 @@ extern PFNGLPROGRAMPARAMETERIPROC ProgramParameteri;
// static int indent = 0;
inline void start_group(const std::string &str) {
if (gl::PushDebugGroup != nullptr) {
gl::PushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, GLsizei(str.size()), str.c_str());
MBGL_CHECK_ERROR(gl::PushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, GLsizei(str.size()), str.c_str()));
} else if (gl::PushGroupMarkerEXT != nullptr) {
gl::PushGroupMarkerEXT(GLsizei(str.size()), str.c_str());
MBGL_CHECK_ERROR(gl::PushGroupMarkerEXT(GLsizei(str.size()), str.c_str()));
}
// fprintf(stderr, "%s%s\n", std::string(indent * 4, ' ').c_str(), str.c_str());
// indent++;
@@ -163,9 +179,9 @@ inline void start_group(const std::string &str) {
inline void end_group() {
if (gl::PopDebugGroup != nullptr) {
gl::PopDebugGroup();
MBGL_CHECK_ERROR(gl::PopDebugGroup());
} else if (gl::PopGroupMarkerEXT != nullptr) {
gl::PopGroupMarkerEXT();
MBGL_CHECK_ERROR(gl::PopGroupMarkerEXT());
}
// indent--;
}
@@ -174,14 +190,11 @@ 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(); };
};
void checkError(const char *cmd, const char *file, int line);
}
}
@@ -190,10 +203,4 @@ void checkError(const char *cmd, const char *file, int line);
#define glDepthRange glDepthRangef
#endif
#if defined(DEBUG)
#define MBGL_CHECK_ERROR(cmd) ([&]() { struct _ { inline ~_() { ::mbgl::gl::checkError(#cmd, __FILE__, __LINE__); } } _; return cmd; }())
#else
#define MBGL_CHECK_ERROR(cmd) (cmd)
#endif
#endif

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -16,6 +16,8 @@ class rwlock;
class loop;
class async;
class worker;
class mutex;
class cond;
}

View File

@@ -0,0 +1,202 @@
#include <mbgl/android/jni.hpp>
#include <mbgl/storage/asset_request.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/util/uv_detail.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 {
struct AssetRequestBaton {
AssetRequestBaton(AssetRequest *request_, const std::string &path, uv_loop_t *loop);
~AssetRequestBaton();
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);
static void run(AssetRequestBaton *ptr);
};
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();
}
AssetRequestBaton::~AssetRequestBaton() {
}
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;
}
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);
}
AssetRequest::AssetRequest(const std::string &path_, uv_loop_t *loop)
: BaseRequest(path_) {
if (!path.empty() && path[0] == '/') {
// This is an absolute path. We don't allow this. Note that this is not a way to absolutely
// prevent access to resources outside the application bundle; e.g. there could be symlinks
// in the application bundle that link to outside. We don't care about these.
response = util::make_unique<Response>();
response->code = 403;
response->message = "Path is outside the application bundle";
notify();
} else {
// Note: The AssetRequestBaton object is deleted in AssetRequestBaton::cleanup().
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();
// Note: The AssetRequestBaton object is deleted in AssetRequestBaton::cleanup().
}
}

View File

@@ -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";
}
}
}

View File

@@ -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());
}
}

View File

@@ -0,0 +1,13 @@
#include <mbgl/platform/platform.hpp>
#include <mbgl/android/jni.hpp>
namespace mbgl {
namespace platform {
// Returns the path to the default shader cache on this system.
std::string defaultShaderCache() {
return mbgl::android::cachePath + "/mbgl-shader-cache-";
}
}
}

View File

@@ -18,7 +18,7 @@ NSURLSession *session = nullptr;
NSString *userAgent = nil;
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;
@@ -144,7 +144,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);

Some files were not shown because too many files have changed in this diff Show More