summaryrefslogtreecommitdiff
path: root/platform/android/MapboxGLAndroidSDK
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK')
-rw-r--r--platform/android/MapboxGLAndroidSDK/build.gradle274
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/deploy.sh2
-rw-r--r--platform/android/MapboxGLAndroidSDK/gradle.properties22
-rw-r--r--platform/android/MapboxGLAndroidSDK/lint.xml7
-rw-r--r--platform/android/MapboxGLAndroidSDK/proguard-rules.pro19
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/AndroidManifest.xml9
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java161
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java185
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java180
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java213
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java223
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/MapFragment.java129
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Annotation.java92
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java251
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindowTipView.java62
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindowView.java38
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Marker.java158
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerOptions.java140
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MultiPoint.java49
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Polygon.java37
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/PolygonOptions.java146
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Polyline.java42
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/PolylineOptions.java145
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Sprite.java40
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/SpriteFactory.java132
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/package-info.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/GeoConstants.java38
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java20
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MathConstants.java22
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java43
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyLocationTracking.java36
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/Style.java55
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/package-info.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/InvalidAccessTokenException.java19
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/SpriteBitmapChangedException.java27
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/TooManySpritesException.java21
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/package-info.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/BoundingBox.java304
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/CoordinateBounds.java58
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/CoordinateRegion.java30
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/CoordinateSpan.java43
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/ILatLng.java12
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/IProjectedMeters.java7
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java187
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngZoom.java93
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/ProjectedMeters.java95
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/package-info.java5
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPContext.java141
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/package-info.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/layers/CustomLayer.java21
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/layers/package-info.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationListener.java13
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationServices.java131
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/package-info.java5
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ApiAccess.java45
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/package-info.java5
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/CompassView.java156
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/MapView.java3846
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java639
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java707
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/package-info.java5
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/overview.html17
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml56
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/color/material_bg_selector.xml5
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/attribution_logo.pngbin0 -> 3408 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/compass.pngbin0 -> 3787 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/default_marker.pngbin0 -> 1520 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/my_location.pngbin0 -> 2783 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/my_location_bearing.pngbin0 -> 5939 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/my_location_stale.pngbin0 -> 2593 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/attribution_logo.pngbin0 -> 1958 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/compass.pngbin0 -> 2488 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/default_marker.pngbin0 -> 1010 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/my_location.pngbin0 -> 2089 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/my_location_bearing.pngbin0 -> 3599 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/my_location_stale.pngbin0 -> 1942 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-v21/bg_default_selector.xml9
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/attribution_logo.pngbin0 -> 4492 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/compass.pngbin0 -> 4775 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/default_marker.pngbin0 -> 1995 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/my_location.pngbin0 -> 3520 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/my_location_bearing.pngbin0 -> 8155 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/my_location_stale.pngbin0 -> 3287 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/attribution_logo.pngbin0 -> 7059 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/compass.pngbin0 -> 7527 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/default_marker.pngbin0 -> 2998 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/my_location.pngbin0 -> 5057 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/my_location_bearing.pngbin0 -> 13246 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/my_location_stale.pngbin0 -> 4676 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/attribution_logo.pngbin0 -> 9402 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/bg_infowindow_content.9.pngbin0 -> 928 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/compass.pngbin0 -> 18537 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/default_marker.pngbin0 -> 4006 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/my_location.pngbin0 -> 5236 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/my_location_bearing.pngbin0 -> 11688 bytes
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/my_location_stale.pngbin0 -> 5053 bytes
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable/bg_default_selector.xml6
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_outline_24dp.xml9
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_outline_24dp_selected.xml9
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_outline_24dp_selector.xml5
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_list_item.xml11
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/layout/fragment_mapview.xml5
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/layout/infowindow_content.xml56
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/layout/infowindow_view.xml4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapview_internal.xml37
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml14
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml90
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml9
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml7
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml18
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml10
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties3
113 files changed, 9989 insertions, 0 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/build.gradle b/platform/android/MapboxGLAndroidSDK/build.gradle
new file mode 100644
index 0000000000..f45d83eb07
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/build.gradle
@@ -0,0 +1,274 @@
+apply plugin: 'android-sdk-manager'
+apply plugin: 'com.android.library'
+apply plugin: 'checkstyle'
+apply plugin: 'maven'
+apply plugin: 'signing'
+
+allprojects {
+ group project.GROUP
+ version project.VERSION_NAME
+
+ repositories {
+ mavenCentral()
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.android.support:support-annotations:23.1.0'
+ compile 'com.android.support:support-v4:23.1.0'
+ compile 'com.android.support:design:23.1.0'
+ compile 'com.squareup.okhttp:okhttp:2.5.0'
+ compile 'com.mapzen.android:lost:1.0.1'
+}
+
+android {
+ compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
+ buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
+
+ defaultConfig {
+ minSdkVersion Integer.parseInt(project.ANDROID_MIN_SDK)
+ targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
+ }
+
+ sourceSets {
+ main.res.srcDirs += 'src/main/res-public'
+ }
+
+ repositories {
+ mavenCentral()
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
+ lintOptions {
+ checkAllWarnings true
+ warningsAsErrors true
+ disable 'InvalidPackage'
+ }
+
+ buildTypes {
+ debug {
+ jniDebuggable true
+ }
+
+ release {
+ jniDebuggable false
+ consumerProguardFiles 'proguard-rules.pro'
+ }
+ }
+}
+
+configurations {
+ all*.exclude group: 'commons-logging', module: 'commons-logging'
+ all*.exclude group: 'commons-collections', module: 'commons-collections'
+}
+
+
+android.libraryVariants.all { variant ->
+ def name = variant.name
+ task "javadoc$name"(type: Javadoc) {
+ description = "Generates javadoc for build $name"
+ failOnError = false
+ destinationDir = new File(destinationDir, variant.baseName)
+ source = files(variant.javaCompile.source)
+ classpath = files(variant.javaCompile.classpath.files) + files(android.bootClasspath)
+ exclude '**/R.java', '**/BuildConfig.java', 'com/almeros/**'
+ options.windowTitle("Mapbox Android SDK $VERSION_NAME Reference")
+ options.docTitle("Mapbox Android SDK $VERSION_NAME")
+ options.header("Mapbox Android SDK $VERSION_NAME Reference")
+ options.bottom("© 2015 Mapbox. All rights reserved.")
+ options.links("http://docs.oracle.com/javase/7/docs/api/")
+ options.linksOffline("http://d.android.com/reference/", "$System.env.ANDROID_HOME/docs/reference")
+ options.overview("src/main/java/overview.html")
+ options.group("Mapbox Android SDK", "com.mapbox.*")
+ options.group("Third Party Libraries", "com.almeros.*")
+ // TODO exclude generated R, BuildConfig, com.almeros.*
+ }
+}
+
+checkstyle {
+ configFile project.file('../checks.xml')
+ showViolations true
+}
+
+/*
+task cleanJNIBuilds {
+ def jniLibsDir = new File("MapboxGLAndroidSDK/src/main/jniLibs")
+ delete jniLibsDir.absolutePath
+}
+*/
+
+android.libraryVariants.all { variant ->
+ def name = variant.buildType.name
+ def checkstyle = project.tasks.create "checkstyle${name.capitalize()}", Checkstyle
+ checkstyle.dependsOn variant.javaCompile
+ checkstyle.source variant.javaCompile.source
+ checkstyle.classpath = project.fileTree(variant.javaCompile.destinationDir)
+ checkstyle.exclude('**/BuildConfig.java')
+ checkstyle.exclude('**/R.java')
+ checkstyle.exclude('**/com/almeros/android/multitouch/**')
+ project.tasks.getByName("check").dependsOn checkstyle
+}
+
+// From https://raw.github.com/mcxiaoke/gradle-mvn-push/master/jar.gradle
+android.libraryVariants.all { variant ->
+ def jarTask = project.tasks.create(name: "jar${variant.name.capitalize()}", type: Jar) {
+ from variant.javaCompile.destinationDir
+ exclude "**/R.class"
+ exclude "**/BuildConfig.class"
+ }
+ jarTask.dependsOn variant.javaCompile
+ artifacts.add('archives', jarTask);
+}
+
+// From https://raw.github.com/mcxiaoke/gradle-mvn-push/master/gradle-mvn-push.gradle
+def isReleaseBuild() {
+ return VERSION_NAME.contains("SNAPSHOT") == false
+}
+
+def getReleaseRepositoryUrl() {
+ return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL :
+ "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
+}
+
+def getSnapshotRepositoryUrl() {
+ return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL :
+ "https://oss.sonatype.org/content/repositories/snapshots/"
+}
+
+def getRepositoryUsername() {
+ return hasProperty('USERNAME') ? USERNAME :
+ (hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "")
+}
+
+def getRepositoryPassword() {
+ return hasProperty('PASSWORD') ? PASSWORD :
+ (hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "")
+}
+
+
+task apklib(type: Zip) {
+ appendix = extension = 'apklib'
+
+ from 'AndroidManifest.xml'
+ into('res') {
+ from 'res'
+ }
+ into('src') {
+ from 'src'
+ }
+}
+
+artifacts {
+ archives apklib
+}
+
+afterEvaluate { project ->
+ uploadArchives {
+ repositories {
+ mavenDeployer {
+ beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+
+ pom.groupId = GROUP
+ pom.artifactId = POM_ARTIFACT_ID
+ pom.version = VERSION_NAME
+
+ repository(url: getReleaseRepositoryUrl()) {
+ authentication(userName: getRepositoryUsername(),
+ password: getRepositoryPassword())
+ }
+ snapshotRepository(url: getSnapshotRepositoryUrl()) {
+ authentication(userName: getRepositoryUsername(),
+ password: getRepositoryPassword())
+ }
+
+/*
+ // Leaving out as artifact was incorrectly named when found
+ addFilter('aar') { artifact, file ->
+ artifact.name == archivesBaseName
+ }
+ addFilter('apklib') { artifact, file ->
+ artifact.name == archivesBaseName + '-apklib'
+ }
+*/
+
+ pom.project {
+ name POM_NAME
+ packaging POM_PACKAGING
+ description POM_DESCRIPTION
+ url POM_URL
+
+ scm {
+ url POM_SCM_URL
+ connection POM_SCM_CONNECTION
+ developerConnection POM_SCM_DEV_CONNECTION
+ }
+
+ licenses {
+ license {
+ name POM_LICENCE_NAME
+ url POM_LICENCE_URL
+ distribution POM_LICENCE_DIST
+ }
+ }
+
+ developers {
+ developer {
+ id POM_DEVELOPER_ID
+ name POM_DEVELOPER_NAME
+ }
+ }
+ }
+ }
+ }
+ }
+
+ signing {
+ required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
+ sign configurations.archives
+ }
+
+ task androidJavadocs(type: Javadoc) {
+ source = android.sourceSets.main.java.sourceFiles
+ classpath = files(android.bootClasspath)
+ }
+
+ task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
+ classifier = 'javadoc'
+ from androidJavadocs.destinationDir
+ }
+
+ task androidSourcesJar(type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.sourceFiles
+ }
+
+ artifacts {
+ archives androidSourcesJar
+ archives androidJavadocsJar
+ }
+}
+
+
+task makeClean(type: Exec) {
+ workingDir '../../'
+ commandLine 'make', 'clean'
+}
+
+task makeAndroid(type: Exec) {
+ workingDir '../../'
+ commandLine 'make', 'android'
+}
+
+task makeAndroidAll(type: Exec) {
+ workingDir '../../'
+ commandLine 'make', 'apackage'
+}
+
diff --git a/platform/android/MapboxGLAndroidSDK/deploy.sh b/platform/android/MapboxGLAndroidSDK/deploy.sh
new file mode 100755
index 0000000000..c9392201e4
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/deploy.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+../gradlew -b build.gradle clean assembleRelease uploadArchives
diff --git a/platform/android/MapboxGLAndroidSDK/gradle.properties b/platform/android/MapboxGLAndroidSDK/gradle.properties
new file mode 100644
index 0000000000..011273a3c5
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/gradle.properties
@@ -0,0 +1,22 @@
+GROUP=com.mapbox.mapboxsdk
+VERSION_NAME=2.4.0-SNAPSHOT
+
+POM_DESCRIPTION=Mapbox GL Android SDK
+POM_URL=https://github.com/mapbox/mapbox-gl-native
+POM_SCM_URL=https://github.com/mapbox/mapbox-gl-native
+POM_SCM_CONNECTION=scm:git@github.com:mapbox/mapbox-gl-native.git
+POM_SCM_DEV_CONNECTION=scm:git@github.com:mapbox/mapbox-gl-native.git
+POM_LICENCE_NAME=The Apache Software License, Version 2.0
+POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
+POM_LICENCE_DIST=repo
+POM_DEVELOPER_ID=mapbox
+POM_DEVELOPER_NAME=Mapbox
+
+ANDROID_MIN_SDK=15
+ANDROID_BUILD_TARGET_SDK_VERSION=23
+ANDROID_BUILD_TOOLS_VERSION=23.0.2
+ANDROID_BUILD_SDK_VERSION=23
+
+POM_NAME=Mapbox GL Android SDK
+POM_ARTIFACT_ID=mapbox-android-sdk
+POM_PACKAGING=aar
diff --git a/platform/android/MapboxGLAndroidSDK/lint.xml b/platform/android/MapboxGLAndroidSDK/lint.xml
new file mode 100644
index 0000000000..3a244199a7
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/lint.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<lint>
+ <!-- Ignore errors caused by OkHTTP -->
+ <issue id="InvalidPackage" severity="ignore">
+ <ignore path="*/okio-1.2.0.jar" />
+ </issue>
+</lint>
diff --git a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro
new file mode 100644
index 0000000000..c805eee199
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro
@@ -0,0 +1,19 @@
+# By default, the flags in this file are appended to flags specified
+# in ../sdk/tools/proguard/proguard-android.txt,
+# contents of this file will be appended into proguard-android.txt
+
+# Square okio, ignoring warnings,
+# see https://github.com/square/okio/issues/60
+-dontwarn okio.**
+
+# Package: annotations
+-keep class com.mapbox.mapboxsdk.annotations.** { *; }
+
+# Package: geometry
+-keep class com.mapbox.mapboxsdk.geometry.** { *; }
+
+# Package: http
+-keep class com.mapbox.mapboxsdk.http.** { *; }
+
+# Package views
+-keep class com.mapbox.mapboxsdk.views.** { *; }
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDK/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..1b8b54c86f
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/AndroidManifest.xml
@@ -0,0 +1,9 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.mapbox.mapboxsdk">
+
+ <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
+</manifest>
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java
new file mode 100644
index 0000000000..622e8f759c
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java
@@ -0,0 +1,161 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
+ * (code.almeros.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+public abstract class BaseGestureDetector {
+ protected final Context mContext;
+ protected boolean mGestureInProgress;
+
+ protected MotionEvent mPrevEvent;
+ protected MotionEvent mCurrEvent;
+
+ protected float mCurrPressure;
+ protected float mPrevPressure;
+ protected long mTimeDelta;
+
+ /**
+ * This value is the threshold ratio between the previous combined pressure
+ * and the current combined pressure. When pressure decreases rapidly
+ * between events the position values can often be imprecise, as it usually
+ * indicates that the user is in the process of lifting a pointer off of the
+ * device. This value was tuned experimentally.
+ */
+ protected static final float PRESSURE_THRESHOLD = 0.67f;
+
+ public BaseGestureDetector(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * All gesture detectors need to be called through this method to be able to
+ * detect gestures. This method delegates work to handler methods
+ * (handleStartProgressEvent, handleInProgressEvent) implemented in
+ * extending classes.
+ *
+ * @param event MotionEvent
+ * @return {@code true} as handled
+ */
+ 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 Action Code from MotionEvent
+ * @param event MotionEvent
+ */
+ 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 Action Code from MotionEvent
+ * @param event MotionEvent
+ */
+ protected abstract void handleInProgressEvent(int actionCode,
+ MotionEvent event);
+
+ protected void updateStateByEvent(MotionEvent curr) {
+ final MotionEvent prev = mPrevEvent;
+
+ // Reset mCurrEvent
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ mCurrEvent = MotionEvent.obtain(curr);
+
+ // Delta time
+ mTimeDelta = curr.getEventTime() - prev.getEventTime();
+
+ // Pressure
+ mCurrPressure = curr.getPressure(curr.getActionIndex());
+ mPrevPressure = prev.getPressure(prev.getActionIndex());
+ }
+
+ protected void resetState() {
+ if (mPrevEvent != null) {
+ mPrevEvent.recycle();
+ mPrevEvent = null;
+ }
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ mGestureInProgress = false;
+ }
+
+ /**
+ * Returns {@code true} if a gesture is currently in progress.
+ *
+ * @return {@code true} if a gesture is currently in progress, {@code false}
+ * otherwise.
+ */
+ public boolean isInProgress() {
+ return mGestureInProgress;
+ }
+
+ /**
+ * Return the time difference in milliseconds between the previous accepted
+ * GestureDetector event and the current GestureDetector event.
+ *
+ * @return Time difference since the last move event in milliseconds.
+ */
+ public long getTimeDelta() {
+ return mTimeDelta;
+ }
+
+ /**
+ * Return the event time of the current GestureDetector event being
+ * processed.
+ *
+ * @return Current GestureDetector event time in milliseconds.
+ */
+ public long getEventTime() {
+ return mCurrEvent.getEventTime();
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java
new file mode 100644
index 0000000000..2430f3f920
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java
@@ -0,0 +1,185 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+/**
+ * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
+ * (code.almeros.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+public class MoveGestureDetector extends BaseGestureDetector {
+
+ /**
+ * Listener which must be implemented which is used by MoveGestureDetector
+ * to perform callbacks to any implementing class which is registered to a
+ * MoveGestureDetector via the constructor.
+ *
+ * @see MoveGestureDetector.SimpleOnMoveGestureListener
+ */
+ public interface OnMoveGestureListener {
+ public boolean onMove(MoveGestureDetector detector);
+
+ public boolean onMoveBegin(MoveGestureDetector detector);
+
+ public void onMoveEnd(MoveGestureDetector detector);
+ }
+
+ /**
+ * Helper class which may be extended and where the methods may be
+ * implemented. This way it is not necessary to implement all methods of
+ * OnMoveGestureListener.
+ */
+ public static class SimpleOnMoveGestureListener implements
+ OnMoveGestureListener {
+ public boolean onMove(MoveGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onMoveBegin(MoveGestureDetector detector) {
+ return true;
+ }
+
+ public void onMoveEnd(MoveGestureDetector detector) {
+ // Do nothing, overridden implementation may be used
+ }
+ }
+
+ private static final PointF FOCUS_DELTA_ZERO = new PointF();
+
+ private final OnMoveGestureListener mListener;
+
+ private PointF mFocusExternal = new PointF();
+ private PointF mFocusDeltaExternal = new PointF();
+
+ public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
+ super(context);
+ mListener = listener;
+ }
+
+ @Override
+ protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_DOWN:
+ resetState(); // In case we missed an UP/CANCEL event
+
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ updateStateByEvent(event);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ mGestureInProgress = mListener.onMoveBegin(this);
+ break;
+ }
+ }
+
+ @Override
+ protected void handleInProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mListener.onMoveEnd(this);
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ updateStateByEvent(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit. This can help filter shaky data as a
+ // finger is lifted.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
+ final boolean updatePrevious = mListener.onMove(this);
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+
+ protected void updateStateByEvent(MotionEvent curr) {
+ super.updateStateByEvent(curr);
+
+ final MotionEvent prev = mPrevEvent;
+
+ // Focus intenal
+ PointF mCurrFocusInternal = determineFocalPoint(curr);
+ PointF mPrevFocusInternal = determineFocalPoint(prev);
+
+ // Focus external
+ // - Prevent skipping of focus delta when a finger is added or removed
+ boolean mSkipNextMoveEvent = prev.getPointerCount() != curr
+ .getPointerCount();
+ mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO
+ : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x,
+ mCurrFocusInternal.y - mPrevFocusInternal.y);
+
+ // - Don't directly use mFocusInternal (or skipping will occur). Add
+ // unskipped delta values to mFocusExternal instead.
+ mFocusExternal.x += mFocusDeltaExternal.x;
+ mFocusExternal.y += mFocusDeltaExternal.y;
+ }
+
+ /**
+ * Determine (multi)finger focal point (a.k.a. center point between all
+ * fingers)
+ *
+ * @param e
+ * @return PointF focal point
+ */
+ private PointF determineFocalPoint(MotionEvent e) {
+ // Number of fingers on screen
+ final int pCount = e.getPointerCount();
+ float x = 0.0f;
+ float y = 0.0f;
+
+ for (int i = 0; i < pCount; i++) {
+ x += e.getX(i);
+ y += e.getY(i);
+ }
+
+ return new PointF(x / pCount, y / pCount);
+ }
+
+ public float getFocusX() {
+ return mFocusExternal.x;
+ }
+
+ public float getFocusY() {
+ return mFocusExternal.y;
+ }
+
+ public PointF getFocusDelta() {
+ return mFocusDeltaExternal;
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java
new file mode 100644
index 0000000000..124fe8509c
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java
@@ -0,0 +1,180 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
+ * (code.almeros.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+public class RotateGestureDetector extends TwoFingerGestureDetector {
+
+ /**
+ * Listener which must be implemented which is used by RotateGestureDetector
+ * to perform callbacks to any implementing class which is registered to a
+ * RotateGestureDetector via the constructor.
+ *
+ * @see RotateGestureDetector.SimpleOnRotateGestureListener
+ */
+ public interface OnRotateGestureListener {
+ public boolean onRotate(RotateGestureDetector detector);
+
+ public boolean onRotateBegin(RotateGestureDetector detector);
+
+ public void onRotateEnd(RotateGestureDetector detector);
+ }
+
+ /**
+ * Helper class which may be extended and where the methods may be
+ * implemented. This way it is not necessary to implement all methods of
+ * OnRotateGestureListener.
+ */
+ public static class SimpleOnRotateGestureListener implements
+ OnRotateGestureListener {
+ public boolean onRotate(RotateGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onRotateBegin(RotateGestureDetector detector) {
+ return true;
+ }
+
+ public void onRotateEnd(RotateGestureDetector detector) {
+ // Do nothing, overridden implementation may be used
+ }
+ }
+
+ private final OnRotateGestureListener mListener;
+ private boolean mSloppyGesture;
+
+ public RotateGestureDetector(Context context,
+ OnRotateGestureListener listener) {
+ super(context);
+ mListener = listener;
+ }
+
+ @Override
+ protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // At least the second finger is on screen now
+
+ resetState(); // In case we missed an UP/CANCEL event
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ updateStateByEvent(event);
+
+ // See if we have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if (!mSloppyGesture) {
+ // No, start gesture now
+ mGestureInProgress = mListener.onRotateBegin(this);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ // See if we still have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if (!mSloppyGesture) {
+ // No, start normal gesture now
+ mGestureInProgress = mListener.onRotateBegin(this);
+ }
+
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ break;
+ }
+ }
+
+ @Override
+ protected void handleInProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_UP:
+ // Gesture ended but
+ updateStateByEvent(event);
+
+ if (!mSloppyGesture) {
+ mListener.onRotateEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (!mSloppyGesture) {
+ mListener.onRotateEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ updateStateByEvent(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit. This can help filter shaky data as a
+ // finger is lifted.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
+ final boolean updatePrevious = mListener.onRotate(this);
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void resetState() {
+ super.resetState();
+ mSloppyGesture = false;
+ }
+
+ /**
+ * Return the rotation difference from the previous rotate event to the
+ * current event.
+ *
+ * @return The current rotation //difference in degrees.
+ */
+ public float getRotationDegreesDelta() {
+ double diffRadians = Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX)
+ - Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX);
+ return (float) (diffRadians * 180.0 / Math.PI);
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java
new file mode 100644
index 0000000000..254597105b
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java
@@ -0,0 +1,213 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * @author Robert Nordan (robert.nordan@norkart.no)
+ *
+ * Copyright (c) 2013, Norkart AS
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+public class ShoveGestureDetector extends TwoFingerGestureDetector {
+
+ /**
+ * Listener which must be implemented which is used by ShoveGestureDetector
+ * to perform callbacks to any implementing class which is registered to a
+ * ShoveGestureDetector via the constructor.
+ *
+ * @see ShoveGestureDetector.SimpleOnShoveGestureListener
+ */
+ public interface OnShoveGestureListener {
+ public boolean onShove(ShoveGestureDetector detector);
+
+ public boolean onShoveBegin(ShoveGestureDetector detector);
+
+ public void onShoveEnd(ShoveGestureDetector detector);
+ }
+
+ /**
+ * Helper class which may be extended and where the methods may be
+ * implemented. This way it is not necessary to implement all methods of
+ * OnShoveGestureListener.
+ */
+ public static class SimpleOnShoveGestureListener implements
+ OnShoveGestureListener {
+ public boolean onShove(ShoveGestureDetector detector) {
+ return false;
+ }
+
+ public boolean onShoveBegin(ShoveGestureDetector detector) {
+ return true;
+ }
+
+ public void onShoveEnd(ShoveGestureDetector detector) {
+ // Do nothing, overridden implementation may be used
+ }
+ }
+
+ private float mPrevAverageY;
+ private float mCurrAverageY;
+
+ private final OnShoveGestureListener mListener;
+ private boolean mSloppyGesture;
+
+ public ShoveGestureDetector(Context context, OnShoveGestureListener listener) {
+ super(context);
+ mListener = listener;
+ }
+
+ @Override
+ protected void handleStartProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // At least the second finger is on screen now
+
+ resetState(); // In case we missed an UP/CANCEL event
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ updateStateByEvent(event);
+
+ // See if we have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if (!mSloppyGesture) {
+ // No, start gesture now
+ mGestureInProgress = mListener.onShoveBegin(this);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ // See if we still have a sloppy gesture
+ mSloppyGesture = isSloppyGesture(event);
+ if (!mSloppyGesture) {
+ // No, start normal gesture now
+ mGestureInProgress = mListener.onShoveBegin(this);
+ }
+
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if (!mSloppyGesture) {
+ break;
+ }
+
+ break;
+ }
+ }
+
+ @Override
+ protected void handleInProgressEvent(int actionCode, MotionEvent event) {
+ switch (actionCode) {
+ case MotionEvent.ACTION_POINTER_UP:
+ // Gesture ended but
+ updateStateByEvent(event);
+
+ if (!mSloppyGesture) {
+ mListener.onShoveEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (!mSloppyGesture) {
+ mListener.onShoveEnd(this);
+ }
+
+ resetState();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ updateStateByEvent(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit. This can help filter shaky data as a
+ // finger is lifted. Also check that shove is meaningful.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD
+ && Math.abs(getShovePixelsDelta()) > 0.5f) {
+ final boolean updatePrevious = mListener.onShove(this);
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void resetState() {
+ super.resetState();
+ mSloppyGesture = false;
+ mPrevAverageY = 0.0f;
+ mCurrAverageY = 0.0f;
+ }
+
+ @Override
+ protected void updateStateByEvent(MotionEvent curr) {
+ super.updateStateByEvent(curr);
+
+ final MotionEvent prev = mPrevEvent;
+ float py0 = prev.getY(0);
+ float py1 = prev.getY(1);
+ mPrevAverageY = (py0 + py1) / 2.0f;
+
+ float cy0 = curr.getY(0);
+ float cy1 = curr.getY(1);
+ mCurrAverageY = (cy0 + cy1) / 2.0f;
+ }
+
+ @Override
+ protected boolean isSloppyGesture(MotionEvent event) {
+ boolean sloppy = super.isSloppyGesture(event);
+ if (sloppy)
+ return true;
+
+ // If it's not traditionally sloppy, we check if the angle between
+ // fingers
+ // is acceptable.
+ double angle = Math.abs(Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX));
+ // about 20 degrees, left or right
+ return !((0.0f < angle && angle < 0.35f) || 2.79f < angle
+ && angle < Math.PI);
+ }
+
+ /**
+ * Return the distance in pixels from the previous shove event to the
+ * current event.
+ *
+ * @return The current distance in pixels.
+ */
+ public float getShovePixelsDelta() {
+ return mCurrAverageY - mPrevAverageY;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java
new file mode 100644
index 0000000000..91e5ef13c8
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java
@@ -0,0 +1,223 @@
+package com.almeros.android.multitouch.gesturedetectors;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie
+ * (code.almeros.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+public abstract class TwoFingerGestureDetector extends BaseGestureDetector {
+
+ private final float mEdgeSlop;
+
+ protected float mPrevFingerDiffX;
+ protected float mPrevFingerDiffY;
+ protected float mCurrFingerDiffX;
+ protected float mCurrFingerDiffY;
+
+ private float mCurrLen;
+ private float mPrevLen;
+
+ private PointF mFocus;
+
+ public TwoFingerGestureDetector(Context context) {
+ super(context);
+
+ ViewConfiguration config = ViewConfiguration.get(context);
+ mEdgeSlop = config.getScaledEdgeSlop();
+ }
+
+ @Override
+ protected abstract void handleStartProgressEvent(int actionCode,
+ MotionEvent event);
+
+ @Override
+ protected abstract void handleInProgressEvent(int actionCode,
+ MotionEvent event);
+
+ protected void updateStateByEvent(MotionEvent curr) {
+ super.updateStateByEvent(curr);
+
+ final MotionEvent prev = mPrevEvent;
+
+ mCurrLen = -1;
+ mPrevLen = -1;
+
+ // Previous
+ final float px0 = prev.getX(0);
+ final float py0 = prev.getY(0);
+ final float px1 = prev.getX(1);
+ final float py1 = prev.getY(1);
+ final float pvx = px1 - px0;
+ final float pvy = py1 - py0;
+ mPrevFingerDiffX = pvx;
+ mPrevFingerDiffY = pvy;
+
+ // Current
+ final float cx0 = curr.getX(0);
+ final float cy0 = curr.getY(0);
+ final float cx1 = curr.getX(1);
+ final float cy1 = curr.getY(1);
+ final float cvx = cx1 - cx0;
+ final float cvy = cy1 - cy0;
+ mCurrFingerDiffX = cvx;
+ mCurrFingerDiffY = cvy;
+ mFocus = determineFocalPoint(curr);
+ }
+
+ /**
+ * Return the current distance between the two pointers forming the gesture
+ * in progress.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpan() {
+ if (mCurrLen == -1) {
+ final float cvx = mCurrFingerDiffX;
+ final float cvy = mCurrFingerDiffY;
+ mCurrLen = (float) Math.sqrt(cvx * cvx + cvy * cvy);
+ }
+ return mCurrLen;
+ }
+
+ /**
+ * Return the previous distance between the two pointers forming the gesture
+ * in progress.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpan() {
+ if (mPrevLen == -1) {
+ final float pvx = mPrevFingerDiffX;
+ final float pvy = mPrevFingerDiffY;
+ mPrevLen = (float) Math.sqrt(pvx * pvx + pvy * pvy);
+ }
+ return mPrevLen;
+ }
+
+ /**
+ * MotionEvent has no getRawX(int) method; simulate it pending future API
+ * approval.
+ *
+ * @param event Motion Event
+ * @param pointerIndex Pointer Index
+ * @return Raw x value or 0
+ */
+ 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 Motion Event
+ * @param pointerIndex Pointer Index
+ * @return Raw y value or 0
+ */
+ 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 Motion Event
+ * @return {@code true} if is sloppy gesture, {@code false} if not
+ */
+ 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 Motion Event
+ * @return PointF focal point
+ */
+ public static PointF determineFocalPoint(MotionEvent e) {
+ // Number of fingers on screen
+ final int pCount = e.getPointerCount();
+ float x = 0.0f;
+ float y = 0.0f;
+
+ for (int i = 0; i < pCount; i++) {
+ x += e.getX(i);
+ y += e.getY(i);
+ }
+
+ return new PointF(x / pCount, y / pCount);
+ }
+
+ public float getFocusX() {
+ return mFocus.x;
+ }
+
+ public float getFocusY() {
+ return mFocus.y;
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java
new file mode 100644
index 0000000000..abc0a11892
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Do not use this package. Used internally by the SDK.
+ */
+package com.almeros.android.multitouch.gesturedetectors;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/MapFragment.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/MapFragment.java
new file mode 100644
index 0000000000..9a1861ec19
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/MapFragment.java
@@ -0,0 +1,129 @@
+package com.mapbox.mapboxsdk;
+
+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.mapboxsdk.utils.ApiAccess;
+import com.mapbox.mapboxsdk.views.MapView;
+
+public class MapFragment extends Fragment {
+
+ //
+ // Static members
+ //
+
+ // Tag used for logging
+ private static final String TAG = "MapFragment";
+
+ //
+ // Instance members
+ //
+
+ // The map
+ private MapView mMap;
+
+ //
+ // Lifecycle events
+ //
+
+ // Called when the fragment is created
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+ Log.v(TAG, "onCreateView");
+
+ // Create the map
+ mMap = (MapView) inflater.inflate(R.layout.fragment_mapview, container, false);
+
+ // Set accessToken
+ mMap.setAccessToken(ApiAccess.getToken(container.getContext()));
+
+ // 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);
+ }
+
+ @Override
+ public void onLowMemory() {
+ Log.v(TAG, "OnLowMemory");
+
+ // Need to pass on to view
+ mMap.onLowMemory();
+ super.onLowMemory();
+ }
+
+ //
+ // Property methods
+ //
+
+ public MapView getMap() {
+ return mMap;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Annotation.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Annotation.java
new file mode 100644
index 0000000000..bf89998788
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Annotation.java
@@ -0,0 +1,92 @@
+package com.mapbox.mapboxsdk.annotations;
+
+import android.support.annotation.NonNull;
+
+import com.mapbox.mapboxsdk.views.MapView;
+
+/**
+ * Annotation is the most general kind of overlay on top of a map,
+ * from which {@link InfoWindow} and {@link Marker} are derived: it manages
+ * attachment to a map and identification, but does not require
+ * content to be placed at a geographical point.
+ */
+public abstract class Annotation implements Comparable<Annotation> {
+
+ /**
+ * <p>
+ * The annotation id
+ * </p>
+ * Internal C++ id is stored as unsigned int.
+ */
+ private long id = -1; // -1 unless added to a MapView
+ private MapView mapView;
+
+ protected Annotation() {
+ }
+
+ /**
+ * <p>
+ * Gets the annotation's unique ID.
+ * </p>
+ * This ID is unique for a MapView instance and is suitable for associating your own extra
+ * data with.
+ */
+ public long getId() {
+ return id;
+ }
+
+ public void remove() {
+ if (mapView == null) {
+ return;
+ }
+ mapView.removeAnnotation(this);
+ }
+
+ /**
+ * Do not use this method. Used internally by the SDK.
+ */
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ /**
+ * Do not use this method. Used internally by the SDK.
+ */
+ public void setMapView(MapView mapView) {
+ this.mapView = mapView;
+ }
+
+ protected MapView getMapView() {
+ if (mapView == null) {
+ return null;
+ }
+ return mapView;
+ }
+
+ @Override
+ public int compareTo(@NonNull Annotation annotation) {
+ if (id < annotation.getId()) {
+ return 1;
+ } else if (id > annotation.getId()) {
+ return -1;
+ }
+
+ // Equal
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Annotation that = (Annotation) o;
+
+ return getId() == that.getId();
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) (getId() ^ (getId() >>> 32));
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java
new file mode 100644
index 0000000000..e0433f00d1
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java
@@ -0,0 +1,251 @@
+package com.mapbox.mapboxsdk.annotations;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.mapbox.mapboxsdk.R;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.views.MapView;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * <p>
+ * A tooltip view. This is a UI element placed over a map at a specific geographic
+ * location.
+ * </p>
+ */
+public class InfoWindow {
+
+ private WeakReference<Marker> mBoundMarker;
+ private WeakReference<MapView> mMapView;
+ private float mMarkerHeightOffset;
+ private float mViewWidthOffset;
+ private PointF mCoordinates;
+ private boolean mIsVisible;
+ protected View mView;
+
+ static int mTitleId = 0;
+ static int mDescriptionId = 0;
+ static int mSubDescriptionId = 0;
+ static int mImageId = 0;
+
+ InfoWindow(int layoutResId, MapView mapView) {
+ View view = LayoutInflater.from(mapView.getContext()).inflate(layoutResId, mapView, false);
+
+ if (mTitleId == 0) {
+ setResIds(mapView.getContext());
+ }
+
+ initialize(view, mapView);
+ }
+
+ InfoWindow(View view, MapView mapView) {
+ initialize(view, mapView);
+ }
+
+ private void initialize(View view, MapView mapView) {
+ mMapView = new WeakReference<>(mapView);
+ mIsVisible = false;
+ mView = view;
+
+ // default behavior: close it when clicking on the tooltip:
+ mView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent e) {
+ if (e.getAction() == MotionEvent.ACTION_UP) {
+ boolean handledDefaultClick = false;
+ MapView.OnInfoWindowClickListener onInfoWindowClickListener =
+ mMapView.get().getOnInfoWindowClickListener();
+ if (onInfoWindowClickListener != null) {
+ handledDefaultClick = onInfoWindowClickListener.onMarkerClick(getBoundMarker());
+ }
+
+ if (!handledDefaultClick) {
+ close();
+ }
+ }
+ return true;
+ }
+ });
+ }
+
+
+ /**
+ * open the window at the specified position.
+ *
+ * @param boundMarker the marker on which is hooked the view
+ * @param position to place the window on the map
+ * @param offsetX (&offsetY) the offset of the view to the position, in pixels.
+ * This allows to offset the view from the object position.
+ * @return this infowindow
+ */
+ InfoWindow open(Marker boundMarker, LatLng position, int offsetX, int offsetY) {
+ setBoundMarker(boundMarker);
+
+ MapView.LayoutParams lp = new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT, MapView.LayoutParams.WRAP_CONTENT);
+ mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+
+ // Calculate y-offset for update method
+ mMarkerHeightOffset = -mView.getMeasuredHeight() + offsetY;
+
+ // Calculate default Android x,y coordinate
+ mCoordinates = mMapView.get().toScreenLocation(position);
+ float x = mCoordinates.x - (mView.getMeasuredWidth() / 2) + offsetX;
+ float y = mCoordinates.y - mView.getMeasuredHeight() + offsetY;
+
+ if (mView instanceof InfoWindowView) {
+ // only apply repositioning/margin for InfoWindowView
+ Resources resources = mMapView.get().getContext().getResources();
+
+ // get right/left popup window
+ float rightSideInfowWindow = x + mView.getMeasuredWidth();
+ float leftSideInfoWindow = x;
+
+ // get right/left map view
+ final float mapRight = mMapView.get().getRight();
+ final float mapLeft = mMapView.get().getLeft();
+
+ float marginHorizontal = resources.getDimension(R.dimen.infowindow_margin);
+ float tipViewOffset = resources.getDimension(R.dimen.infowindow_tipview_width) / 2;
+ float tipViewMarginLeft = mView.getMeasuredWidth() / 2 - tipViewOffset;
+
+ boolean outOfBoundsLeft = false;
+ boolean outOfBoundsRight = false;
+
+ // if out of bounds right
+ if (rightSideInfowWindow > mapRight) {
+ outOfBoundsRight = true;
+ x -= rightSideInfowWindow - mapRight;
+ tipViewMarginLeft += rightSideInfowWindow - mapRight + tipViewOffset;
+ rightSideInfowWindow = x + mView.getMeasuredWidth();
+ }
+
+ // fit screen left
+ if (leftSideInfoWindow < mapLeft) {
+ outOfBoundsLeft = true;
+ x += mapLeft - leftSideInfoWindow;
+ tipViewMarginLeft -= mapLeft - leftSideInfoWindow + tipViewOffset;
+ leftSideInfoWindow = x;
+ }
+
+ // Add margin right
+ if (outOfBoundsRight && mapRight - rightSideInfowWindow < marginHorizontal) {
+ x -= marginHorizontal - (mapRight - rightSideInfowWindow);
+ tipViewMarginLeft += marginHorizontal - (mapRight - rightSideInfowWindow) - tipViewOffset;
+ leftSideInfoWindow = x;
+ }
+
+ // Add margin left
+ if (outOfBoundsLeft && leftSideInfoWindow - mapLeft < marginHorizontal) {
+ x += marginHorizontal - (leftSideInfoWindow - mapLeft);
+ tipViewMarginLeft -= (marginHorizontal - (leftSideInfoWindow - mapLeft)) - tipViewOffset;
+ }
+
+ // Adjust tipView
+ InfoWindowView infoWindowView = (InfoWindowView) mView;
+ infoWindowView.setTipViewMarginLeft((int) tipViewMarginLeft);
+ }
+
+ // set anchor popupwindowview
+ mView.setX(x);
+ mView.setY(y);
+
+ // Calculate x-offset for update method
+ mViewWidthOffset = x - mCoordinates.x - offsetX;
+
+ close(); //if it was already opened
+ mMapView.get().addView(mView, lp);
+ mIsVisible = true;
+ return this;
+ }
+
+ /**
+ * Close this InfoWindow if it is visible, otherwise don't do anything.
+ *
+ * @return this info window
+ */
+ InfoWindow close() {
+ if (mIsVisible) {
+ mIsVisible = false;
+ ((ViewGroup) mView.getParent()).removeView(mView);
+ setBoundMarker(null);
+ onClose();
+ }
+ return this;
+ }
+
+ /**
+ * Constructs the view that is displayed when the InfoWindow opens.
+ * This retrieves data from overlayItem and shows it in the tooltip.
+ *
+ * @param overlayItem the tapped overlay item
+ */
+ void adaptDefaultMarker(Marker overlayItem) {
+ String title = overlayItem.getTitle();
+ ((TextView) mView.findViewById(mTitleId /*R.id.title*/)).setText(title);
+ String snippet = overlayItem.getSnippet();
+ ((TextView) mView.findViewById(mDescriptionId /*R.id.description*/)).setText(snippet);
+
+/*
+ //handle sub-description, hiding or showing the text view:
+ TextView subDescText = (TextView) mView.findViewById(mSubDescriptionId);
+ String subDesc = overlayItem.getSubDescription();
+ if ("".equals(subDesc)) {
+ subDescText.setVisibility(View.GONE);
+ } else {
+ subDescText.setText(subDesc);
+ subDescText.setVisibility(View.VISIBLE);
+ }
+*/
+ }
+
+ private void onClose() {
+ mMapView.get().deselectMarker(getBoundMarker());
+ }
+
+ InfoWindow setBoundMarker(Marker boundMarker) {
+ mBoundMarker = new WeakReference<>(boundMarker);
+ return this;
+ }
+
+ Marker getBoundMarker() {
+ if (mBoundMarker == null) {
+ return null;
+ }
+ return mBoundMarker.get();
+ }
+
+ /**
+ * Given a context, set the resource ids for the layout
+ * of the InfoWindow.
+ *
+ * @param context the apps Context
+ */
+ private static void setResIds(Context context) {
+ String packageName = context.getPackageName(); //get application package name
+ mTitleId = context.getResources().getIdentifier("id/infowindow_title", null, packageName);
+ mDescriptionId =
+ context.getResources().getIdentifier("id/infowindow_description", null, packageName);
+ mSubDescriptionId = context.getResources()
+ .getIdentifier("id/infowindow_subdescription", null, packageName);
+ mImageId = context.getResources().getIdentifier("id/infowindow_image", null, packageName);
+ }
+
+ public void update() {
+ MapView mapView = mMapView.get();
+ Marker marker = mBoundMarker.get();
+ if (mapView != null && marker != null) {
+ mCoordinates = mapView.toScreenLocation(marker.getPosition());
+ mView.setX(mCoordinates.x + mViewWidthOffset);
+ mView.setY(mCoordinates.y + mMarkerHeightOffset);
+ }
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindowTipView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindowTipView.java
new file mode 100644
index 0000000000..d2afafc59d
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindowTipView.java
@@ -0,0 +1,62 @@
+package com.mapbox.mapboxsdk.annotations;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.mapbox.mapboxsdk.R;
+
+final class InfoWindowTipView extends View {
+
+ private Paint mPaint;
+ private Path mPath;
+ private int mLineWidth;
+
+ public InfoWindowTipView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mPath = new Path();
+ mLineWidth = (int) context.getResources().getDimension(R.dimen.infowindow_line_width);
+ mPaint = new Paint();
+ mPaint.setColor(Color.WHITE);
+ mPaint.setAntiAlias(true);
+ mPaint.setStrokeWidth(0.0f);
+ mPaint.setStyle(Paint.Style.FILL);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ int height = getMeasuredHeight();
+ int width = getMeasuredWidth();
+
+ mPath.rewind();
+
+ this.mPaint.setColor(Color.WHITE);
+ this.mPaint.setAntiAlias(true);
+ this.mPaint.setStrokeWidth(0.0f);
+ this.mPaint.setStyle(Paint.Style.FILL);
+
+ mPath.moveTo(0, 0);
+ mPath.lineTo(width, 0);
+ mPath.lineTo((width / 2), height);
+ mPath.lineTo(0, 0);
+ canvas.drawPath(mPath, this.mPaint);
+
+ mPath.rewind();
+
+ this.mPaint.setColor(Color.parseColor("#C2C2C2"));
+ this.mPaint.setAntiAlias(true);
+ this.mPaint.setStrokeWidth(mLineWidth);
+ this.mPaint.setStyle(Paint.Style.STROKE);
+
+ mPath.moveTo(0, 0);
+ mPath.lineTo(width / 2, height);
+ mPath.lineTo(width, 0);
+ canvas.drawPath(mPath, this.mPaint);
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindowView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindowView.java
new file mode 100644
index 0000000000..80dc5931a7
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindowView.java
@@ -0,0 +1,38 @@
+package com.mapbox.mapboxsdk.annotations;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.widget.RelativeLayout;
+
+import com.mapbox.mapboxsdk.R;
+
+class InfoWindowView extends RelativeLayout {
+
+ private InfoWindowTipView mTipView;
+
+ public InfoWindowView(Context context) {
+ this(context, null);
+ }
+
+ public InfoWindowView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public InfoWindowView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize(context);
+ }
+
+ private void initialize(Context context) {
+ LayoutInflater.from(context).inflate(R.layout.infowindow_content, this);
+ mTipView = (InfoWindowTipView) findViewById(R.id.infowindow_tipview);
+ }
+
+ void setTipViewMarginLeft(int marginLeft) {
+ RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mTipView.getLayoutParams();
+ layoutParams.leftMargin = marginLeft;
+ // This is a bit of a hack but prevents an occasional gap between the InfoWindow
+ layoutParams.topMargin = (int) getResources().getDimension(R.dimen.infowindow_offset);
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Marker.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Marker.java
new file mode 100644
index 0000000000..0f626a2618
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Marker.java
@@ -0,0 +1,158 @@
+package com.mapbox.mapboxsdk.annotations;
+
+import android.support.annotation.Nullable;
+import android.view.View;
+
+import com.mapbox.mapboxsdk.R;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.views.MapView;
+
+/**
+ * A marker is a map overlay that shows an icon image at a specific
+ * geographical location and can be associated with a {@link InfoWindow}
+ * that is shown when the marker is tapped.
+ */
+public final class Marker extends Annotation {
+
+ private LatLng position;
+ private String snippet;
+ private Sprite icon;
+ private String title;
+ private InfoWindow infoWindow = null;
+ private boolean infoWindowShown = false;
+ private int topOffsetPixels;
+
+ /**
+ * Constructor
+ */
+ Marker() {
+ super();
+ }
+
+ public LatLng getPosition() {
+ return position;
+ }
+
+ public String getSnippet() {
+ return snippet;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Do not use this method. Used internally by the SDK.
+ */
+ public void hideInfoWindow() {
+ if (infoWindow != null) {
+ infoWindow.close();
+ }
+ infoWindowShown = false;
+ }
+
+ /**
+ * Do not use this method. Used internally by the SDK.
+ */
+ public boolean isInfoWindowShown() {
+ return infoWindowShown;
+ }
+
+ void setPosition(LatLng position) {
+ this.position = position;
+ }
+
+ void setSnippet(String snippet) {
+ this.snippet = snippet;
+ }
+
+ /**
+ * Do not use this method. Used internally by the SDK.
+ */
+ public void setIcon(@Nullable Sprite icon) {
+ this.icon = icon;
+ }
+
+ public Sprite getIcon() {
+ return icon;
+ }
+
+ void setTitle(String title) {
+ this.title = title;
+ }
+
+ /**
+ * Do not use this method. Used internally by the SDK.
+ */
+ public InfoWindow showInfoWindow() {
+ if (getMapView() == null) {
+ return null;
+ }
+
+ MapView.InfoWindowAdapter infoWindowAdapter = getMapView().getInfoWindowAdapter();
+ if (infoWindowAdapter != null) {
+ // end developer is using a custom InfoWindowAdapter
+ View content = infoWindowAdapter.getInfoWindow(this);
+ if (content != null) {
+ infoWindow = new InfoWindow(content, getMapView());
+ showInfoWindow(infoWindow);
+ return infoWindow;
+ }
+ }
+
+ getInfoWindow().adaptDefaultMarker(this);
+ return showInfoWindow(getInfoWindow());
+ }
+
+ private InfoWindow showInfoWindow(InfoWindow iw) {
+ iw.open(this, getPosition(), 0, topOffsetPixels);
+ infoWindowShown = true;
+ return iw;
+ }
+
+ private InfoWindow getInfoWindow() {
+ if (infoWindow == null) {
+ infoWindow = new InfoWindow(R.layout.infowindow_view, getMapView());
+ }
+ return infoWindow;
+ }
+
+ /*
+ @Override
+ void setVisible(boolean visible) {
+ super.setVisible(visible);
+ if (!visible && infoWindowShown) {
+ hideInfoWindow();
+ }
+ }
+ */
+
+ /**
+ * Do not use this method. Used internally by the SDK.
+ */
+ public void setTopOffsetPixels(int topOffsetPixels) {
+ this.topOffsetPixels = topOffsetPixels;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ Marker marker = (Marker) o;
+ return !(getPosition() != null ? !getPosition().equals(marker.getPosition()) : marker.getPosition() != null);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (getPosition() != null ? getPosition().hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Marker [position[" + getPosition() + "]]";
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerOptions.java
new file mode 100644
index 0000000000..b5bb24a49d
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerOptions.java
@@ -0,0 +1,140 @@
+package com.mapbox.mapboxsdk.annotations;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
+
+import com.mapbox.mapboxsdk.geometry.LatLng;
+
+
+/**
+ * <p>
+ * A builder object for the options configuration {@link com.mapbox.mapboxsdk.annotations.Marker} and
+ * {@link com.mapbox.mapboxsdk.annotations.InfoWindow}
+ * instances on maps.
+ * </p>
+ *
+ * <h3>Example</h3>
+ *
+ * <pre>
+ * mMapView.addMarker(new MarkerOptions()
+ * .title("Intersection")
+ * .snippet("H St NW with 15th St NW")
+ * .position(new LatLng(38.9002073, -77.03364419)));
+ * </pre>
+ */
+public final class MarkerOptions implements Parcelable {
+
+ public static final Parcelable.Creator<MarkerOptions> CREATOR
+ = new Parcelable.Creator<MarkerOptions>() {
+ public MarkerOptions createFromParcel(Parcel in) {
+ return new MarkerOptions(in);
+ }
+
+ public MarkerOptions[] newArray(int size) {
+ return new MarkerOptions[size];
+ }
+ };
+
+ private MarkerOptions(Parcel in) {
+ marker = new Marker();
+ position((LatLng) in.readParcelable(LatLng.class.getClassLoader()));
+ snippet(in.readString());
+ String spriteId = in.readString();
+ Bitmap spriteBitmap = in.readParcelable(Bitmap.class.getClassLoader());
+ Sprite icon = new Sprite(spriteId, spriteBitmap);
+ icon(icon);
+ title(in.readString());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(getPosition(), flags);
+ out.writeString(getSnippet());
+ out.writeString(getIcon().getId());
+ out.writeParcelable(getIcon().getBitmap(), flags);
+ out.writeString(getTitle());
+ }
+
+ private Marker marker;
+
+ public MarkerOptions() {
+ marker = new Marker();
+ }
+
+ /**
+ * Do not use this method. Used internally by the SDK.
+ */
+ public Marker getMarker() {
+ return marker;
+ }
+
+ public LatLng getPosition() {
+ return marker.getPosition();
+ }
+
+ public String getSnippet() {
+ return marker.getSnippet();
+ }
+
+ public String getTitle() {
+ return marker.getTitle();
+ }
+
+ public Sprite getIcon() {
+ return marker.getIcon();
+ }
+
+ public MarkerOptions position(LatLng position) {
+ marker.setPosition(position);
+ return this;
+ }
+
+ public MarkerOptions snippet(String snippet) {
+ marker.setSnippet(snippet);
+ return this;
+ }
+
+ public MarkerOptions icon(@Nullable Sprite icon) {
+ marker.setIcon(icon);
+ return this;
+ }
+
+ public MarkerOptions title(String title) {
+ marker.setTitle(title);
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ MarkerOptions marker = (MarkerOptions) o;
+
+ if (getPosition() != null ? !getPosition().equals(marker.getPosition()) : marker.getPosition() != null)
+ return false;
+ if (getSnippet() != null ? !getSnippet().equals(marker.getSnippet()) : marker.getSnippet() != null)
+ return false;
+ if (getIcon() != null ? !getIcon().equals(marker.getIcon()) : marker.getIcon() != null)
+ return false;
+ return !(getTitle() != null ? !getTitle().equals(marker.getTitle()) : marker.getTitle() != null);
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 31 * result + (getPosition() != null ? getPosition().hashCode() : 0);
+ result = 31 * result + (getSnippet() != null ? getSnippet().hashCode() : 0);
+ result = 31 * result + (getIcon() != null ? getIcon().hashCode() : 0);
+ result = 31 * result + (getTitle() != null ? getTitle().hashCode() : 0);
+ return result;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MultiPoint.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MultiPoint.java
new file mode 100644
index 0000000000..5c1dfb119f
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MultiPoint.java
@@ -0,0 +1,49 @@
+package com.mapbox.mapboxsdk.annotations;
+
+import com.mapbox.mapboxsdk.geometry.LatLng;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class MultiPoint extends Annotation {
+
+ private List<LatLng> points;
+ private float alpha = 1.0f;
+
+ protected MultiPoint() {
+ super();
+ points = new ArrayList<>();
+ }
+
+ /**
+ * Returns a copy of the points.
+ *
+ * @return points - as a copy
+ */
+ public List<LatLng> getPoints() {
+ return new ArrayList<>(points);
+ }
+
+ /**
+ * Sets the points of this polyline. This method will take a copy
+ * of the points, so further mutations to points will have no effect
+ * on this polyline.
+ *
+ * @param points the points of the polyline
+ */
+ void setPoints(List<LatLng> points) {
+ this.points = new ArrayList<>(points);
+ }
+
+ void addPoint(LatLng point) {
+ points.add(point);
+ }
+
+ public float getAlpha() {
+ return alpha;
+ }
+
+ void setAlpha(float alpha) {
+ this.alpha = alpha;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Polygon.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Polygon.java
new file mode 100644
index 0000000000..4a07b16827
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Polygon.java
@@ -0,0 +1,37 @@
+package com.mapbox.mapboxsdk.annotations;
+
+import android.graphics.Color;
+
+import com.mapbox.mapboxsdk.geometry.LatLng;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Polygon is a geometry annotation that's a closed loop of coordinates.
+ */
+public final class Polygon extends MultiPoint {
+
+ private int fillColor = Color.BLACK; // default fillColor is black
+ private int strokeColor = Color.BLACK; // default strokeColor is black
+
+ Polygon() {
+ super();
+ }
+
+ public int getFillColor() {
+ return fillColor;
+ }
+
+ public int getStrokeColor() {
+ return strokeColor;
+ }
+
+ void setFillColor(int color) {
+ fillColor = color;
+ }
+
+ void setStrokeColor(int color) {
+ strokeColor = color;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/PolygonOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/PolygonOptions.java
new file mode 100644
index 0000000000..c716d10edf
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/PolygonOptions.java
@@ -0,0 +1,146 @@
+package com.mapbox.mapboxsdk.annotations;
+
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.mapbox.mapboxsdk.geometry.LatLng;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class PolygonOptions implements Parcelable {
+
+ public static final Parcelable.Creator<PolygonOptions> CREATOR
+ = new Parcelable.Creator<PolygonOptions>() {
+ public PolygonOptions createFromParcel(Parcel in) {
+ return new PolygonOptions(in);
+ }
+
+ public PolygonOptions[] newArray(int size) {
+ return new PolygonOptions[size];
+ }
+ };
+
+ private PolygonOptions(Parcel in) {
+ polygon = new Polygon();
+ ArrayList<LatLng> pointsList = new ArrayList<>();
+ in.readList(pointsList, LatLng.class.getClassLoader());
+ addAll(pointsList);
+ alpha(in.readFloat());
+ fillColor(in.readInt());
+ strokeColor(in.readInt());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeList(getPoints());
+ out.writeFloat(getAlpha());
+ out.writeInt(getFillColor());
+ out.writeInt(getStrokeColor());
+ }
+
+ private Polygon polygon;
+
+ public PolygonOptions() {
+ polygon = new Polygon();
+ }
+
+ public PolygonOptions add(LatLng point) {
+ polygon.addPoint(point);
+ return this;
+ }
+
+ public PolygonOptions add(LatLng... points) {
+ for (LatLng point : points) {
+ add(point);
+ }
+ return this;
+ }
+
+ public PolygonOptions addAll(Iterable<LatLng> points) {
+ for (LatLng point : points) {
+ add(point);
+ }
+ return this;
+ }
+
+ public PolygonOptions alpha(float alpha) {
+ polygon.setAlpha(alpha);
+ return this;
+ }
+
+ public float getAlpha() {
+ return polygon.getAlpha();
+ }
+
+ /**
+ * Sets the color of the polygon.
+ *
+ * @param color - the color in ARGB format
+ * @return PolygonOptions - the options object
+ */
+ public PolygonOptions fillColor(int color) {
+ polygon.setFillColor(color);
+ return this;
+ }
+
+ public int getFillColor() {
+ return polygon.getFillColor();
+ }
+
+ /**
+ * Do not use this method. Used internally by the SDK.
+ */
+ public Polygon getPolygon() {
+ return polygon;
+ }
+
+ /**
+ * Sets the color of the stroke of the polygon.
+ *
+ * @param color - the color in ARGB format
+ * @return PolygonOptions - the options object
+ */
+ public PolygonOptions strokeColor(int color) {
+ polygon.setStrokeColor(color);
+ return this;
+ }
+
+ public int getStrokeColor() {
+ return polygon.getStrokeColor();
+ }
+
+ public List<LatLng> getPoints() {
+ // the getter gives us a copy, which is the safe thing to do...
+ return polygon.getPoints();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ PolygonOptions polygon = (PolygonOptions) o;
+
+ if (Float.compare(polygon.getAlpha(), getAlpha()) != 0) return false;
+ if (getFillColor() != polygon.getFillColor()) return false;
+ if (getStrokeColor() != polygon.getStrokeColor()) return false;
+ return !(getPoints() != null ? !getPoints().equals(polygon.getPoints()) : polygon.getPoints() != null);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 31 * result + (getAlpha() != +0.0f ? Float.floatToIntBits(getAlpha()) : 0);
+ result = 31 * result + getFillColor();
+ result = 31 * result + getStrokeColor();
+ result = 31 * result + (getPoints() != null ? getPoints().hashCode() : 0);
+ return result;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Polyline.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Polyline.java
new file mode 100644
index 0000000000..cfaf0d21d9
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Polyline.java
@@ -0,0 +1,42 @@
+package com.mapbox.mapboxsdk.annotations;
+
+import android.graphics.Color;
+
+/**
+ * Polyline is a geometry feature with an unclosed list of coordinates drawn as a line
+ */
+public final class Polyline extends MultiPoint {
+
+ private int color = Color.BLACK; // default color is black
+ private float width = 10; // As specified by Google API Docs (in pixels)
+
+ Polyline() {
+ super();
+ }
+
+ public int getColor() {
+ return color;
+ }
+
+ public float getWidth() {
+ return width;
+ }
+
+ /**
+ * Sets the color of the polyline.
+ *
+ * @param color - the color in ARGB format
+ */
+ void setColor(int color) {
+ this.color = color;
+ }
+
+ /**
+ * Sets the width of the polyline.
+ *
+ * @param width in pixels
+ */
+ void setWidth(float width) {
+ this.width = width;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/PolylineOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/PolylineOptions.java
new file mode 100644
index 0000000000..d48858c7d9
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/PolylineOptions.java
@@ -0,0 +1,145 @@
+package com.mapbox.mapboxsdk.annotations;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.mapbox.mapboxsdk.geometry.LatLng;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class PolylineOptions implements Parcelable {
+
+
+ public static final Parcelable.Creator<PolylineOptions> CREATOR
+ = new Parcelable.Creator<PolylineOptions>() {
+ public PolylineOptions createFromParcel(Parcel in) {
+ return new PolylineOptions(in);
+ }
+
+ public PolylineOptions[] newArray(int size) {
+ return new PolylineOptions[size];
+ }
+ };
+
+ private PolylineOptions(Parcel in) {
+ polyline = new Polyline();
+ ArrayList<LatLng> pointsList = new ArrayList<>();
+ in.readList(pointsList, LatLng.class.getClassLoader());
+ addAll(pointsList);
+ alpha(in.readFloat());
+ color(in.readInt());
+ width(in.readFloat());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeList(getPoints());
+ out.writeFloat(getAlpha());
+ out.writeInt(getColor());
+ out.writeFloat(getWidth());
+ }
+
+ private Polyline polyline;
+
+ public PolylineOptions() {
+ polyline = new Polyline();
+ }
+
+ public PolylineOptions add(LatLng point) {
+ polyline.addPoint(point);
+ return this;
+ }
+
+ public PolylineOptions add(LatLng... points) {
+ for (LatLng point : points) {
+ add(point);
+ }
+ return this;
+ }
+
+ public PolylineOptions addAll(Iterable<LatLng> points) {
+ for (LatLng point : points) {
+ add(point);
+ }
+ return this;
+ }
+
+ public PolylineOptions alpha(float alpha) {
+ polyline.setAlpha(alpha);
+ return this;
+ }
+
+ public float getAlpha() {
+ return polyline.getAlpha();
+ }
+
+ /**
+ * Sets the color of the polyline.
+ *
+ * @param color - the color in ARGB format
+ */
+ public PolylineOptions color(int color) {
+ polyline.setColor(color);
+ return this;
+ }
+
+ public int getColor() {
+ return polyline.getColor();
+ }
+
+ /**
+ * Do not use this method. Used internally by the SDK.
+ */
+ public Polyline getPolyline() {
+ return polyline;
+ }
+
+ public float getWidth() {
+ return polyline.getWidth();
+ }
+
+ /**
+ * Sets the width of the polyline.
+ *
+ * @param width in pixels
+ * @return a new PolylineOptions
+ */
+ public PolylineOptions width(float width) {
+ polyline.setWidth(width);
+ return this;
+ }
+
+ public List<LatLng> getPoints() {
+ // the getter gives us a copy, which is the safe thing to do...
+ return polyline.getPoints();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ PolylineOptions polyline = (PolylineOptions) o;
+
+ if (Float.compare(polyline.getAlpha(), getAlpha()) != 0) return false;
+ if (getColor() != polyline.getColor()) return false;
+ if (Float.compare(polyline.getWidth(), getWidth()) != 0) return false;
+ return !(getPoints() != null ? !getPoints().equals(polyline.getPoints()) : polyline.getPoints() != null);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 31 * result + (getAlpha() != +0.0f ? Float.floatToIntBits(getAlpha()) : 0);
+ result = 31 * result + getColor();
+ result = 31 * result + (getWidth() != +0.0f ? Float.floatToIntBits(getWidth()) : 0);
+ result = 31 * result + (getPoints() != null ? getPoints().hashCode() : 0);
+ return result;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Sprite.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Sprite.java
new file mode 100644
index 0000000000..597c196d2a
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Sprite.java
@@ -0,0 +1,40 @@
+package com.mapbox.mapboxsdk.annotations;
+
+import android.graphics.Bitmap;
+
+public final class Sprite {
+ private Bitmap mBitmap;
+ private String mId;
+
+ Sprite(String id, Bitmap bitmap) {
+ mId = id;
+ mBitmap = bitmap;
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Sprite sprite = (Sprite) o;
+
+ if (!mBitmap.equals(sprite.mBitmap)) return false;
+ return mId.equals(sprite.mId);
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mBitmap.hashCode();
+ result = 31 * result + mId.hashCode();
+ return result;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/SpriteFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/SpriteFactory.java
new file mode 100644
index 0000000000..3395c5039d
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/SpriteFactory.java
@@ -0,0 +1,132 @@
+package com.mapbox.mapboxsdk.annotations;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+import com.mapbox.mapboxsdk.R;
+import com.mapbox.mapboxsdk.exceptions.TooManySpritesException;
+import com.mapbox.mapboxsdk.views.MapView;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public final class SpriteFactory {
+
+ private static final String SPRITE_ID_PREFIX = "com.mapbox.sprites.sprite_";
+
+ private MapView mMapView;
+ private Sprite mDefaultMarker;
+ private BitmapFactory.Options mOptions;
+
+ private int mNextId = 0;
+
+ public SpriteFactory(MapView mapView) {
+ mMapView = mapView;
+ DisplayMetrics realMetrics = null;
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager wm = (WindowManager) mMapView.getContext().getSystemService(Context.WINDOW_SERVICE);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ realMetrics = new DisplayMetrics();
+ wm.getDefaultDisplay().getRealMetrics(realMetrics);
+ }
+ wm.getDefaultDisplay().getMetrics(metrics);
+
+ mOptions = new BitmapFactory.Options();
+ mOptions.inScaled = true;
+ mOptions.inDensity = DisplayMetrics.DENSITY_DEFAULT;
+ mOptions.inTargetDensity = metrics.densityDpi;
+ if (realMetrics != null) {
+ mOptions.inScreenDensity = realMetrics.densityDpi;
+ }
+
+ }
+
+ public Sprite fromBitmap(Bitmap bitmap) {
+ if (bitmap == null) {
+ return null;
+ }
+
+ if (mNextId < 0) {
+ throw new TooManySpritesException();
+ }
+ String id = SPRITE_ID_PREFIX + ++mNextId;
+
+ return new Sprite(id, bitmap);
+ }
+
+ public Sprite fromDrawable(Drawable drawable) {
+ int width = drawable.getIntrinsicWidth();
+ int height = drawable.getIntrinsicHeight();
+
+ return fromDrawable(drawable, width, height);
+ }
+
+
+ public Sprite fromDrawable(Drawable drawable, int width, int height) {
+ if ((width < 0) || (height < 0)) {
+ return null;
+ }
+
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ Rect temp = drawable.getBounds();
+ Rect bounds = new Rect(0, 0, width, height);
+ drawable.setBounds(bounds);
+ drawable.draw(canvas);
+ drawable.setBounds(temp);
+
+ return fromBitmap(bitmap);
+ }
+
+ public Sprite fromResource(int resourceId) {
+ Bitmap bitmap = BitmapFactory.decodeResource(mMapView.getResources(), resourceId);
+ return fromBitmap(bitmap);
+ }
+
+ public Sprite defaultMarker() {
+ if (mDefaultMarker == null) {
+ mDefaultMarker = fromResource(R.drawable.default_marker);
+ }
+ return mDefaultMarker;
+ }
+
+ private Sprite fromInputStream(InputStream is) {
+ Bitmap bitmap = BitmapFactory.decodeStream(is, null, mOptions);
+ return fromBitmap(bitmap);
+ }
+
+ public Sprite fromAsset(String assetName) {
+ InputStream is;
+ try {
+ is = mMapView.getContext().getAssets().open(assetName);
+ } catch (IOException e) {
+ return null;
+ }
+ return fromInputStream(is);
+ }
+
+ public Sprite fromPath(String absolutePath) {
+ Bitmap bitmap = BitmapFactory.decodeFile(absolutePath, mOptions);
+ return fromBitmap(bitmap);
+ }
+
+ public Sprite fromFile(String fileName) {
+ FileInputStream is;
+ try {
+ is = mMapView.getContext().openFileInput(fileName);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ return fromInputStream(is);
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/package-info.java
new file mode 100644
index 0000000000..575d10c564
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * This package contains classes to add and manage annotations and markers in your map.
+ */
+package com.mapbox.mapboxsdk.annotations;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/GeoConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/GeoConstants.java
new file mode 100644
index 0000000000..ed6f77f419
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/GeoConstants.java
@@ -0,0 +1,38 @@
+package com.mapbox.mapboxsdk.constants;
+
+/**
+ * GeoConstants exposes constants for doing locational calculations on Earth
+ */
+public class GeoConstants {
+
+ /**
+ * The <a href='http://en.wikipedia.org/wiki/Earth_radius#Equatorial_radius'>equatorial radius</a>
+ * value in meters
+ */
+ public static final int RADIUS_EARTH_METERS = 6378137;
+
+ /**
+ * The minimum latitude on Earth. This is the minimum latitude representable
+ * by Mapbox GL's Mercator projection, because the projection distorts latitude
+ * near the poles towards infinity.
+ */
+ public static final double MIN_LATITUDE = -85.05112878;
+
+ /**
+ * The maximum latitude on Earth. This is the maximum latitude representable
+ * by Mapbox GL's Mercator projection, because the projection distorts latitude
+ * near the poles towards infinity.
+ */
+ public static final double MAX_LATITUDE = 85.05112878;
+
+ /**
+ * The minimum longitude on Earth
+ */
+ public static final double MIN_LONGITUDE = -180;
+
+ /**
+ * The maximum longitude on Earth
+ */
+ public static final double MAX_LONGITUDE = 180;
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java
new file mode 100644
index 0000000000..33b3d46a63
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java
@@ -0,0 +1,20 @@
+package com.mapbox.mapboxsdk.constants;
+
+import java.util.Locale;
+
+/**
+ * MapboxConstants exposes Mapbox related constants
+ */
+public class MapboxConstants {
+
+ /**
+ * Default Locale for data processing (ex: String.toLowerCase(MAPBOX_LOCALE, "foo"))
+ */
+ public static final Locale MAPBOX_LOCALE = Locale.US;
+
+ /**
+ * Key used to store access token in AndroidManifest.xml
+ */
+ public static final String KEY_META_DATA_MANIFEST = "com.mapbox.AccessToken";
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MathConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MathConstants.java
new file mode 100644
index 0000000000..6e320cc9f6
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MathConstants.java
@@ -0,0 +1,22 @@
+package com.mapbox.mapboxsdk.constants;
+
+/**
+ * MathConstants exposes math related constant values
+ */
+public class MathConstants {
+
+ /**
+ * Constant used to convert degrees to radials
+ */
+ public static final double DEG2RAD = (Math.PI / 180.0);
+
+ /**
+ * Constant used to convert radials to degrees
+ */
+ public static final double RAD2DEG = (180.0 / Math.PI);
+
+ /**
+ * The number PI
+ */
+ public static final double PI = Math.PI;
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java
new file mode 100644
index 0000000000..a70d37dedc
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java
@@ -0,0 +1,43 @@
+package com.mapbox.mapboxsdk.constants;
+
+import android.support.annotation.IntDef;
+
+import com.mapbox.mapboxsdk.views.MapView;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * MyBearingTracking exposes different types bearing tracking modes.
+ *
+ * @see MapView#setMyBearingTrackingMode(int)
+ * @see com.mapbox.mapboxsdk.views.UserLocationView#setMyBearingTrackingMode(int)
+ */
+public class MyBearingTracking {
+
+ /**
+ * Indicates the parameter accepts one of the values from {@link MyBearingTracking}.
+ */
+ @IntDef({NONE, COMPASS, GPS, /**COMBINED**/})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Mode {
+ }
+
+ /**
+ * Bearing tracking is disabled
+ */
+ public static final int NONE = 0x00000000;
+
+ /**
+ * Tracking the bearing of the user based on sensor data
+ */
+ public static final int COMPASS = 0x00000004;
+
+ /**
+ * Tracking the bearing of the user based on GPS data
+ */
+ public static final int GPS = 0x00000008;
+
+ //public static final int COMBINED = 0x00000012;
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyLocationTracking.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyLocationTracking.java
new file mode 100644
index 0000000000..130ca36f76
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyLocationTracking.java
@@ -0,0 +1,36 @@
+package com.mapbox.mapboxsdk.constants;
+
+import android.support.annotation.IntDef;
+
+import com.mapbox.mapboxsdk.views.MapView;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * MyLocationTracking exposes different types of locational tracking modes.
+ *
+ * @see MapView#setMyLocationTrackingMode(int)
+ * @see com.mapbox.mapboxsdk.views.UserLocationView#setMyLocationTrackingMode(int)
+ */
+public class MyLocationTracking {
+
+ /**
+ * Indicates the parameter accepts one of the values from {@link MyLocationTracking}.
+ */
+ @IntDef({TRACKING_NONE, TRACKING_FOLLOW})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Mode {
+ }
+
+ /**
+ * Location tracking is disabled.
+ */
+ public static final int TRACKING_NONE = 0x00000000;
+
+ /**
+ * Tracking the location of the user, {@link MapView} will reposition to center of {@link com.mapbox.mapboxsdk.views.UserLocationView}
+ */
+ public static final int TRACKING_FOLLOW = 0x00000004;
+
+} \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/Style.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/Style.java
new file mode 100644
index 0000000000..11ee9c3155
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/Style.java
@@ -0,0 +1,55 @@
+package com.mapbox.mapboxsdk.constants;
+
+import android.support.annotation.StringDef;
+
+import com.mapbox.mapboxsdk.views.MapView;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+/**
+ * <p>
+ * Style provides URLs to several professional styles designed by Mapbox.
+ * </p>
+ * These styles are all ready to go in your app. To load one, pass it into {@link MapView#setStyleUrl(String)}
+ *
+ * @see MapView#setStyleUrl(String)
+ */
+public class Style {
+
+ /**
+ * Indicates the parameter accepts one of the values from {@link Style}.
+ */
+ @StringDef({MAPBOX_STREETS, EMERALD, LIGHT, DARK, SATELLITE, SATELLITE_STREETS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StyleUrl {
+ }
+
+ // IMPORTANT: If you change any of these you also need to edit them in strings.xml
+
+ /**
+ * Mapbox Streets: A complete basemap, perfect for incorporating your own data.
+ */
+ public static final String MAPBOX_STREETS = "mapbox://styles/mapbox/streets-v8";
+ /**
+ * Emerald: A versatile style, with emphasis on road networks and public transit.
+ */
+ public static final String EMERALD = "mapbox://styles/mapbox/emerald-v8";
+ /**
+ * Light: Subtle light backdrop for data visualizations.
+ */
+ public static final String LIGHT = "mapbox://styles/mapbox/light-v8";
+ /**
+ * Dark: Subtle dark backdrop for data visualizations.
+ */
+ public static final String DARK = "mapbox://styles/mapbox/dark-v8";
+ /**
+ * Satellite: A beautiful global satellite and aerial imagery layer.
+ */
+ public static final String SATELLITE = "mapbox://styles/mapbox/satellite-v8";
+
+ /**
+ * Satellite Streets: Global satellite and aerial imagery with unobtrusive labels.
+ */
+ public static final String SATELLITE_STREETS = "mapbox://styles/mapbox/satellite-hybrid-v8";
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/package-info.java
new file mode 100644
index 0000000000..b99119db0a
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * This package provides access to several map related constants and bundled styles.
+ */
+package com.mapbox.mapboxsdk.constants;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/InvalidAccessTokenException.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/InvalidAccessTokenException.java
new file mode 100644
index 0000000000..889df322aa
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/InvalidAccessTokenException.java
@@ -0,0 +1,19 @@
+package com.mapbox.mapboxsdk.exceptions;
+
+import android.os.Bundle;
+import com.mapbox.mapboxsdk.views.MapView;
+
+/**
+ * A {@code InvalidAccessTokenException} is thrown by {@link MapView} when there is either no access
+ * token set before {@link MapView#onCreate(Bundle)} or an invalid access token is set in {@link MapView#setAccessToken(String)}
+ *
+ * @see MapView#onCreate(Bundle)
+ * @see MapView#setAccessToken(String)
+ */
+public class InvalidAccessTokenException extends RuntimeException {
+
+ public InvalidAccessTokenException() {
+ super("Using MapView requires setting a valid access token. See the INSTALL.md");
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/SpriteBitmapChangedException.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/SpriteBitmapChangedException.java
new file mode 100644
index 0000000000..03c5f914b1
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/SpriteBitmapChangedException.java
@@ -0,0 +1,27 @@
+package com.mapbox.mapboxsdk.exceptions;
+
+import android.graphics.Bitmap;
+
+import com.mapbox.mapboxsdk.annotations.Marker;
+import com.mapbox.mapboxsdk.annotations.Sprite;
+import com.mapbox.mapboxsdk.views.MapView;
+
+/**
+ * <p>
+ * A {@code SpriteBitmapChangedException} is thrown by {@link MapView} when a {@link Marker} is added
+ * that has a {@link Sprite} with a {@link Bitmap} that has been modified.
+ * </p>
+ * You cannot modify a {@code Sprite} after it has been added to the map in a {@code Marker}
+ *
+ * @see MapView
+ * @see Sprite
+ * @see Marker
+ */
+public class SpriteBitmapChangedException extends RuntimeException {
+
+ public SpriteBitmapChangedException() {
+ super("The added Marker has a Sprite with a Bitmap that has been modified. You cannot modufy" +
+ "a Sprite after it has been added in a Marker.");
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/TooManySpritesException.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/TooManySpritesException.java
new file mode 100644
index 0000000000..d36c99edd2
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/TooManySpritesException.java
@@ -0,0 +1,21 @@
+package com.mapbox.mapboxsdk.exceptions;
+
+import com.mapbox.mapboxsdk.annotations.Sprite;
+import com.mapbox.mapboxsdk.annotations.SpriteFactory;
+
+/**
+ * <p>
+ * A {@code TooManySpritesException} is thrown by {@link SpriteFactory} when it
+ * cannot create a {@link Sprite} because there are already too many.
+ * </p>
+ * You should try to reuse Sprite objects whenever possible.
+ *
+ * @see SpriteFactory
+ */
+public class TooManySpritesException extends RuntimeException {
+
+ public TooManySpritesException() {
+ super("Cannot create a Sprite because there are already too many. Try reusing Sprites.");
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/package-info.java
new file mode 100644
index 0000000000..d593884ee3
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * This package contains exceptions thrown in this SDK.
+ */
+package com.mapbox.mapboxsdk.exceptions;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/BoundingBox.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/BoundingBox.java
new file mode 100644
index 0000000000..e778e30aad
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/BoundingBox.java
@@ -0,0 +1,304 @@
+package com.mapbox.mapboxsdk.geometry;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * A rectangular geographical area defined in latitude and longitude units.
+ */
+public final class BoundingBox implements Parcelable, Serializable {
+
+ private final double mLatNorth;
+ private final double mLatSouth;
+ private final double mLonEast;
+ private final double mLonWest;
+
+ private final boolean mIsValid;
+
+ /**
+ * Construct a new bounding box based on its corners, given in NESW
+ * order.
+ *
+ * @param northLatitude Northern Latitude
+ * @param eastLongitude Eastern Longitude
+ * @param southLatitude Southern Latitude
+ * @param westLongitude Western Longitude
+ */
+ public BoundingBox(final double northLatitude, final double eastLongitude, final double southLatitude, final double westLongitude) {
+ this.mLatNorth = northLatitude;
+ this.mLonEast = eastLongitude;
+ this.mLatSouth = southLatitude;
+ this.mLonWest = westLongitude;
+ this.mIsValid = ((this.mLonWest < this.mLonEast) && (this.mLatNorth > this.mLatSouth));
+ }
+
+ /**
+ * Construct a new bounding box based on its corners, given in NESW order.
+ *
+ * @param northEast Coordinate
+ * @param southWest Coordinate
+ */
+ public BoundingBox(final LatLng northEast, final LatLng southWest) {
+ this(northEast.getLatitude(), northEast.getLongitude(), southWest.getLatitude(), southWest.getLongitude());
+ }
+
+ /**
+ * Create a bounding box from another bounding box
+ *
+ * @param other the other bounding box
+ */
+ public BoundingBox(final BoundingBox other) {
+ this.mLatNorth = other.getLatNorth();
+ this.mLonEast = other.getLonEast();
+ this.mLatSouth = other.getLatSouth();
+ this.mLonWest = other.getLonWest();
+ this.mIsValid = other.isValid();
+ }
+
+ /**
+ * Create a new BoundingBox with no size centered at 0, 0, also known as null island
+ */
+ public BoundingBox() {
+ this(0, 0, 0, 0);
+ }
+
+ /**
+ * Calculates the centerpoint of this bounding box by simple interpolation and returns
+ * it as a point. This is a non-geodesic calculation which is not the geographic center.
+ *
+ * @return LatLng center of this BoundingBox
+ */
+ public LatLng getCenter() {
+ return new LatLng((this.mLatNorth + this.mLatSouth) / 2,
+ (this.mLonEast + this.mLonWest) / 2);
+ }
+
+ public double getLatNorth() {
+ return this.mLatNorth;
+ }
+
+ public double getLatSouth() {
+ return this.mLatSouth;
+ }
+
+ public double getLonEast() {
+ return this.mLonEast;
+ }
+
+ public double getLonWest() {
+ return this.mLonWest;
+ }
+
+ public boolean isValid() {
+ return this.mIsValid;
+ }
+
+ /**
+ * Get the area spanned by this bounding box
+ *
+ * @return CoordinateSpan area
+ */
+ public CoordinateSpan getSpan() {
+ return new CoordinateSpan(getLatitudeSpan(), getLongitudeSpan());
+ }
+
+ /**
+ * Get the absolute distance, in degrees, between the north and
+ * south boundaries of this bounding box
+ *
+ * @return Span distance
+ */
+ public double getLatitudeSpan() {
+ return Math.abs(this.mLatNorth - this.mLatSouth);
+ }
+
+ /**
+ * Get the absolute distance, in degrees, between the west and
+ * east boundaries of this bounding box
+ *
+ * @return Span distance
+ */
+ public double getLongitudeSpan() {
+ return Math.abs(this.mLonEast - this.mLonWest);
+ }
+
+
+ /**
+ * Validate if bounding box is empty, determined if absolute distance is
+ *
+ * @return boolean indicating if span is empty
+ */
+ public boolean isEmpty() {
+ return getLongitudeSpan() == 0.0 || getLatitudeSpan() == 0.0;
+ }
+
+ @Override
+ public String toString() {
+ return "N:" + this.mLatNorth + "; E:" + this.mLonEast + "; S:" + this.mLatSouth + "; W:" + this.mLonWest;
+ }
+
+ /**
+ * Constructs a bounding box that contains all of a list of LatLng
+ * objects. Empty lists will yield invalid bounding boxes.
+ *
+ * @param latLngs List of LatLng objects
+ * @return BoundingBox
+ */
+ public static BoundingBox fromLatLngs(final List<? extends ILatLng> latLngs) {
+ double minLat = 90,
+ minLon = 180,
+ maxLat = -90,
+ maxLon = -180;
+
+ for (final ILatLng gp : latLngs) {
+ final double latitude = gp.getLatitude();
+ final double longitude = gp.getLongitude();
+
+ minLat = Math.min(minLat, latitude);
+ minLon = Math.min(minLon, longitude);
+ maxLat = Math.max(maxLat, latitude);
+ maxLon = Math.max(maxLon, longitude);
+ }
+
+ return new BoundingBox(maxLat, maxLon, minLat, minLon);
+ }
+
+ /**
+ * Determines whether this bounding box matches another one via coordinates.
+ *
+ * @param o another object
+ * @return a boolean indicating whether the bounding boxes are equal
+ */
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o instanceof BoundingBox) {
+ BoundingBox other = (BoundingBox) o;
+ return mLatNorth == other.getLatNorth()
+ && mLatSouth == other.getLatSouth()
+ && mLonEast == other.getLonEast()
+ && mLonWest == other.getLonWest();
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether this bounding box contains a point and the point
+ * does not touch its boundary.
+ *
+ * @param pGeoPoint the point which may be contained
+ * @return true, if the point is contained within the box.
+ */
+ public boolean contains(final ILatLng pGeoPoint) {
+ final double latitude = pGeoPoint.getLatitude();
+ final double longitude = pGeoPoint.getLongitude();
+ return ((latitude < this.mLatNorth)
+ && (latitude > this.mLatSouth))
+ && ((longitude < this.mLonEast)
+ && (longitude > this.mLonWest));
+ }
+
+ /**
+ * Returns a new BoundingBox that stretches to contain both this and another BoundingBox.
+ *
+ * @param box BoundingBox to add
+ * @return BoundingBox
+ */
+ public BoundingBox union(BoundingBox box) {
+ return union(box.getLatNorth(), box.getLonEast(), box.getLatSouth(), box.getLonWest());
+ }
+
+ /**
+ * Returns a new BoundingBox that stretches to include another bounding box,
+ * given by corner points.
+ *
+ * @param lonNorth Northern Longitude
+ * @param latEast Eastern Latitude
+ * @param lonSouth Southern Longitude
+ * @param latWest Western Longitude
+ * @return BoundingBox
+ */
+ public BoundingBox union(final double lonNorth, final double latEast, final double lonSouth, final double latWest) {
+ return new BoundingBox((this.mLatNorth < lonNorth) ? lonNorth : this.mLatNorth,
+ (this.mLonEast < latEast) ? latEast : this.mLonEast,
+ (this.mLatSouth > lonSouth) ? lonSouth : this.mLatSouth,
+ (this.mLonWest > latWest) ? latWest : this.mLonWest);
+ }
+
+ /**
+ * Returns a new BoundingBox that is the intersection of this with another box
+ *
+ * @param box BoundingBox to intersect with
+ * @return BoundingBox
+ */
+ public BoundingBox intersect(BoundingBox box) {
+ double minLatWest = Math.max(getLonWest(), box.getLonWest());
+ double maxLatEast = Math.min(getLonEast(), box.getLonEast());
+ if (maxLatEast > minLatWest) {
+ double minLonSouth = Math.max(getLatSouth(), box.getLatSouth());
+ double maxLonNorth = Math.min(getLatNorth(), box.getLatNorth());
+ if (maxLonNorth > minLonSouth) {
+ return new BoundingBox(maxLonNorth, maxLatEast, minLonSouth, minLatWest);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a new BoundingBox that is the intersection of this with another box
+ *
+ * @param northLongitude Northern Longitude
+ * @param eastLatitude Eastern Latitude
+ * @param southLongitude Southern Longitude
+ * @param westLatitude Western Latitude
+ * @return BoundingBox
+ */
+ public BoundingBox intersect(double northLongitude, double eastLatitude, double southLongitude, double westLatitude) {
+ return intersect(new BoundingBox(northLongitude, eastLatitude, southLongitude, westLatitude));
+ }
+
+ public static final Parcelable.Creator<BoundingBox> CREATOR =
+ new Parcelable.Creator<BoundingBox>() {
+ @Override
+ public BoundingBox createFromParcel(final Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ public BoundingBox[] newArray(final int size) {
+ return new BoundingBox[size];
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ return (int) ((mLatNorth + 90)
+ + ((mLatSouth + 90) * 1000)
+ + ((mLonEast + 180) * 1000000)
+ + ((mLonEast + 180) * 1000000000));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(final Parcel out, final int arg1) {
+ out.writeDouble(this.mLatNorth);
+ out.writeDouble(this.mLonEast);
+ out.writeDouble(this.mLatSouth);
+ out.writeDouble(this.mLonWest);
+ }
+
+ private static BoundingBox readFromParcel(final Parcel in) {
+ final double lonNorth = in.readDouble();
+ final double latEast = in.readDouble();
+ final double lonSouth = in.readDouble();
+ final double latWest = in.readDouble();
+ return new BoundingBox(lonNorth, latEast, lonSouth, latWest);
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/CoordinateBounds.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/CoordinateBounds.java
new file mode 100644
index 0000000000..1ad584f8c8
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/CoordinateBounds.java
@@ -0,0 +1,58 @@
+package com.mapbox.mapboxsdk.geometry;
+
+/**
+ * Implementation of iOS MGLCoordinateBounds
+ */
+public class CoordinateBounds {
+
+ private LatLng southWest;
+ private LatLng northEast;
+
+ public CoordinateBounds(LatLng southWest, LatLng northEast) {
+ this.southWest = southWest;
+ this.northEast = northEast;
+ }
+
+ public LatLng getSouthWest() {
+ return southWest;
+ }
+
+ public void setSouthWest(LatLng southWest) {
+ this.southWest = southWest;
+ }
+
+ public LatLng getNorthEast() {
+ return northEast;
+ }
+
+ public void setNorthEast(LatLng northEast) {
+ this.northEast = northEast;
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ long temp;
+ temp = southWest.hashCode();
+ result = (int) (temp ^ (temp >>> 32));
+ temp = northEast.hashCode();
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o instanceof CoordinateBounds) {
+ CoordinateBounds other = (CoordinateBounds) o;
+ return getNorthEast().equals(other.getNorthEast())
+ && getSouthWest().equals(other.getSouthWest());
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "CoordinateBounds [northEast[" + getNorthEast() + "], southWest[]" + getSouthWest() + "]";
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/CoordinateRegion.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/CoordinateRegion.java
new file mode 100644
index 0000000000..a70bb05a41
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/CoordinateRegion.java
@@ -0,0 +1,30 @@
+package com.mapbox.mapboxsdk.geometry;
+
+/**
+ * Implementation of iOS MKCoordinateRegion
+ */
+public class CoordinateRegion {
+ private LatLng center;
+ private CoordinateSpan span;
+
+ public CoordinateRegion(final LatLng center, final CoordinateSpan span) {
+ this.center = center;
+ this.span = span;
+ }
+
+ public LatLng getCenter() {
+ return center;
+ }
+
+ public void setCenter(final LatLng center) {
+ this.center = center;
+ }
+
+ public CoordinateSpan getSpan() {
+ return span;
+ }
+
+ public void setSpan(final CoordinateSpan span) {
+ this.span = span;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/CoordinateSpan.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/CoordinateSpan.java
new file mode 100644
index 0000000000..79361f725a
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/CoordinateSpan.java
@@ -0,0 +1,43 @@
+package com.mapbox.mapboxsdk.geometry;
+
+/**
+ * Implementation of iOS MKCoordinateSpan
+ */
+public class CoordinateSpan {
+
+ private double latitudeSpan;
+ private double longitudeSpan;
+
+ public CoordinateSpan(final double latitudeSpan, final double longitudeSpan) {
+ this.latitudeSpan = latitudeSpan;
+ this.longitudeSpan = longitudeSpan;
+ }
+
+ public double getLatitudeSpan() {
+ return latitudeSpan;
+ }
+
+ public void setLatitudeSpan(final double latitudeSpan) {
+ this.latitudeSpan = latitudeSpan;
+ }
+
+ public double getLongitudeSpan() {
+ return longitudeSpan;
+ }
+
+ public void setLongitudeSpan(final double longitudeSpan) {
+ this.longitudeSpan = longitudeSpan;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o instanceof CoordinateSpan) {
+ CoordinateSpan other = (CoordinateSpan) o;
+ return longitudeSpan == other.getLongitudeSpan()
+ && latitudeSpan == other.getLatitudeSpan();
+ }
+ return false;
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/ILatLng.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/ILatLng.java
new file mode 100644
index 0000000000..892d0ad4ae
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/ILatLng.java
@@ -0,0 +1,12 @@
+package com.mapbox.mapboxsdk.geometry;
+
+/**
+ * A Latitude, Longitude point.
+ */
+public interface ILatLng {
+ double getLatitude();
+
+ double getLongitude();
+
+ double getAltitude();
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/IProjectedMeters.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/IProjectedMeters.java
new file mode 100644
index 0000000000..5aa5f607eb
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/IProjectedMeters.java
@@ -0,0 +1,7 @@
+package com.mapbox.mapboxsdk.geometry;
+
+public interface IProjectedMeters {
+ double getNorthing();
+
+ double getEasting();
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java
new file mode 100644
index 0000000000..c340b09e05
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java
@@ -0,0 +1,187 @@
+package com.mapbox.mapboxsdk.geometry;
+
+import android.location.Location;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.mapbox.mapboxsdk.constants.GeoConstants;
+import com.mapbox.mapboxsdk.constants.MathConstants;
+
+import java.io.Serializable;
+
+/**
+ * <p>
+ * This class is container for a single latitude, longitude pair, with
+ * optional altitude. Latitude and longitude are expressed as decimal degrees
+ * in the WGS84 datum. By default, altitude is 0.0, or sea level.
+ * </p>
+ * <p>
+ * Mapbox GL displays maps in the Mercator Projection and projects geographical
+ * data automatically, so all data enters in the WGS84 datum.
+ * </p>
+ */
+public class LatLng implements ILatLng, Parcelable, Serializable {
+
+ public static final Parcelable.Creator<LatLng> CREATOR = new Parcelable.Creator<LatLng>() {
+ public LatLng createFromParcel(Parcel in) {
+ return new LatLng(in);
+ }
+
+ public LatLng[] newArray(int size) {
+ return new LatLng[size];
+ }
+ };
+
+ private double latitude;
+ private double longitude;
+ private double altitude = 0.0;
+
+ /**
+ * Construct a new latitude, longitude point at (0, 0)
+ */
+ public LatLng() {
+ this.latitude = 0.0;
+ this.longitude = 0.0;
+ }
+
+ /**
+ * Construct a new latitude, longitude point given float arguments
+ * @param latitude Latitude in degrees
+ * @param longitude Longitude in degrees
+ */
+ public LatLng(double latitude, double longitude) {
+ this.latitude = latitude;
+ this.longitude = longitude;
+ }
+
+ /**
+ * Construct a new latitude, longitude, altitude point given float arguments
+ * @param latitude Latitude in degrees
+ * @param longitude Longitude in degress
+ * @param altitude Altitude in meters
+ */
+ public LatLng(double latitude, double longitude, double altitude) {
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.altitude = altitude;
+ }
+
+ /**
+ * Transform a Location into a LatLng point
+ * @param location Android Location
+ */
+ public LatLng(Location location) {
+ this(location.getLatitude(), location.getLongitude(), location.getAltitude());
+ }
+
+ /**
+ * Clone an existing latitude longitude point
+ * @param aLatLng LatLng
+ */
+ public LatLng(LatLng aLatLng) {
+ this.latitude = aLatLng.latitude;
+ this.longitude = aLatLng.longitude;
+ this.altitude = aLatLng.altitude;
+ }
+
+ protected LatLng(Parcel in) {
+ latitude = in.readDouble();
+ longitude = in.readDouble();
+ altitude = in.readDouble();
+ }
+
+ public void setLatitude(double latitude) {
+ this.latitude = latitude;
+ }
+
+ @Override
+ public double getLatitude() {
+ return latitude;
+ }
+
+ public void setLongitude(double longitude) {
+ this.longitude = longitude;
+ }
+
+ @Override
+ public double getLongitude() {
+ return longitude;
+ }
+
+ public void setAltitude(double altitude) {
+ this.altitude = altitude;
+ }
+
+ @Override
+ public double getAltitude() {
+ return altitude;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ LatLng latLng = (LatLng) o;
+
+ return Double.compare(latLng.altitude, altitude) == 0 && Double.compare(latLng.latitude, latitude) == 0 && Double.compare(latLng.longitude, longitude) == 0;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ long temp;
+ temp = Double.doubleToLongBits(latitude);
+ result = (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(longitude);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(altitude);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "LatLng [longitude=" + longitude + ", latitude=" + latitude + ", altitude=" + altitude + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeDouble(latitude);
+ out.writeDouble(longitude);
+ out.writeDouble(altitude);
+ }
+
+ /**
+ * Calculate distance between two points
+ * @param other Other LatLng to compare to
+ * @return distance in meters
+ */
+ public double distanceTo(LatLng other) {
+
+ final double a1 = MathConstants.DEG2RAD * this.latitude;
+ final double a2 = MathConstants.DEG2RAD * this.longitude;
+ final double b1 = MathConstants.DEG2RAD * other.getLatitude();
+ final double b2 = MathConstants.DEG2RAD * other.getLongitude();
+
+ final double cosa1 = Math.cos(a1);
+ final double cosb1 = Math.cos(b1);
+
+ final double t1 = cosa1 * Math.cos(a2) * cosb1 * Math.cos(b2);
+ final double t2 = cosa1 * Math.sin(a2) * cosb1 * Math.sin(b2);
+ final double t3 = Math.sin(a1) * Math.sin(b1);
+ final double tt = Math.acos(t1 + t2 + t3);
+
+ return GeoConstants.RADIUS_EARTH_METERS * tt;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngZoom.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngZoom.java
new file mode 100644
index 0000000000..9e453a391c
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngZoom.java
@@ -0,0 +1,93 @@
+package com.mapbox.mapboxsdk.geometry;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.Serializable;
+
+public class LatLngZoom extends LatLng implements Parcelable, Serializable {
+
+ public static final Parcelable.Creator<LatLngZoom> CREATOR = new Parcelable.Creator<LatLngZoom>() {
+ public LatLngZoom createFromParcel(Parcel in) {
+ return new LatLngZoom(in);
+ }
+
+ public LatLngZoom[] newArray(int size) {
+ return new LatLngZoom[size];
+ }
+ };
+
+ private double zoom;
+
+ public LatLngZoom() {
+ super();
+ this.zoom = 0.0;
+ }
+
+ public LatLngZoom(double latitude, double longitude, double zoom) {
+ super(latitude, longitude);
+ this.zoom = zoom;
+ }
+
+ public LatLngZoom(LatLng latLng, double zoom) {
+ super(latLng.getLatitude(), latLng.getLongitude());
+ this.zoom = zoom;
+ }
+
+ private LatLngZoom(Parcel in) {
+ super(in);
+ zoom = in.readDouble();
+ }
+
+ public double getZoom() {
+ return zoom;
+ }
+
+ public void setZoom(double zoom) {
+ this.zoom = zoom;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ LatLngZoom that = (LatLngZoom) o;
+
+ return Double.compare(that.zoom, zoom) == 0;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ long temp;
+ temp = Double.doubleToLongBits(zoom);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "LatLngZoom [latitude=" + super.getLatitude() + ", longitude=" + super.getLongitude() + ", altitude=" + super.getAltitude() + ", zoom=" + zoom + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeDouble(zoom);
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/ProjectedMeters.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/ProjectedMeters.java
new file mode 100644
index 0000000000..fc821e887c
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/ProjectedMeters.java
@@ -0,0 +1,95 @@
+package com.mapbox.mapboxsdk.geometry;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.Serializable;
+
+/**
+ * ProjectedMeters is an internal representation of longitude, latitude points that
+ * have been projected into Mapbox GL's Mercator projection. Instead of decimal
+ * degrees, it uses Mercator meters (which are notably not equivalent to SI meters)
+ * except at the equator.
+ */
+public class ProjectedMeters implements IProjectedMeters, Parcelable, Serializable {
+
+ public static final Creator<ProjectedMeters> CREATOR = new Creator<ProjectedMeters>() {
+ public ProjectedMeters createFromParcel(Parcel in) {
+ return new ProjectedMeters(in);
+ }
+
+ public ProjectedMeters[] newArray(int size) {
+ return new ProjectedMeters[size];
+ }
+ };
+
+ private double northing;
+ private double easting;
+
+ public ProjectedMeters(double northing, double easting) {
+ this.northing = northing;
+ this.easting = easting;
+ }
+
+ public ProjectedMeters(ProjectedMeters aProjectedMeters) {
+ this.northing = aProjectedMeters.northing;
+ this.easting = aProjectedMeters.easting;
+ }
+
+ protected ProjectedMeters(Parcel in) {
+ northing = in.readDouble();
+ easting = in.readDouble();
+ }
+
+ @Override
+ public double getNorthing() {
+ return northing;
+ }
+
+ @Override
+ public double getEasting() {
+ return easting;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ProjectedMeters projectedMeters = (ProjectedMeters) o;
+
+ return Double.compare(projectedMeters.easting, easting) == 0 && Double.compare(projectedMeters.northing, northing) == 0;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ long temp;
+ temp = Double.doubleToLongBits(easting);
+ result = (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(northing);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ProjectedMeters [northing=" + northing + ", easting=" + easting + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeDouble(northing);
+ out.writeDouble(easting);
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/package-info.java
new file mode 100644
index 0000000000..bf7dae12a8
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * This package contains classes that deal with geometry and map coordinates. Many SDK functions
+ * accept or return these classes.
+ */
+package com.mapbox.mapboxsdk.geometry;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPContext.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPContext.java
new file mode 100644
index 0000000000..886c7b9af8
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPContext.java
@@ -0,0 +1,141 @@
+package com.mapbox.mapboxsdk.http;
+
+import android.util.Log;
+
+import com.mapbox.mapboxsdk.constants.MapboxConstants;
+import com.squareup.okhttp.Call;
+import com.squareup.okhttp.Callback;
+import com.squareup.okhttp.Interceptor;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.Response;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.ProtocolException;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+import javax.net.ssl.SSLException;
+
+class HTTPContext {
+
+ private static final int CONNECTION_ERROR = 0;
+ private static final int TEMPORARY_ERROR = 1;
+ private static final int PERMANENT_ERROR = 2;
+ private static final int CANCELED_ERROR = 3;
+
+ private static HTTPContext mInstance = null;
+
+ private OkHttpClient mClient;
+
+ private HTTPContext() {
+ super();
+ mClient = new OkHttpClient();
+ //mClient.interceptors().add(new LoggingInterceptor());
+ }
+
+ public static HTTPContext getInstance() {
+ if (mInstance == null) {
+ mInstance = new HTTPContext();
+ }
+
+ return mInstance;
+ }
+
+ public HTTPRequest createRequest(long nativePtr, String resourceUrl, String userAgent, String etag, String modified) {
+ return new HTTPRequest(nativePtr, resourceUrl, userAgent, etag, modified);
+ }
+
+ public class HTTPRequest implements Callback {
+ private final String LOG_TAG = HTTPRequest.class.getName();
+
+ private long mNativePtr = 0;
+
+ private Call mCall;
+ private Request mRequest;
+
+ private native void nativeOnFailure(long nativePtr, int type, String message);
+ private native void nativeOnResponse(long nativePtr, int code, String message, String etag, String modified, String cacheControl, String expires, byte[] body);
+
+ private HTTPRequest(long nativePtr, String resourceUrl, String userAgent, String etag, String modified) {
+ mNativePtr = nativePtr;
+ Request.Builder builder = new Request.Builder().url(resourceUrl).tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)).addHeader("User-Agent", userAgent);
+ if (etag.length() > 0) {
+ builder = builder.addHeader("If-None-Match", etag);
+ } else if (modified.length() > 0) {
+ builder = builder.addHeader("If-Modified-Since", modified);
+ }
+ mRequest = builder.build();
+ }
+
+ public void start() {
+ mCall = HTTPContext.getInstance().mClient.newCall(mRequest);
+ mCall.enqueue(this);
+ }
+
+ public void cancel() {
+ mCall.cancel();
+ }
+
+ @Override
+ public void onFailure(Request request, IOException e) {
+ Log.d(LOG_TAG, "onFailure: " + e.getMessage());
+
+ int type = PERMANENT_ERROR;
+ if ((e instanceof UnknownHostException) || (e instanceof SocketException) || (e instanceof ProtocolException) || (e instanceof SSLException)) {
+ type = CONNECTION_ERROR;
+ } else if ((e instanceof InterruptedIOException)) {
+ type = TEMPORARY_ERROR;
+ } else if (mCall.isCanceled()) {
+ type = CANCELED_ERROR;
+ }
+
+ nativeOnFailure(mNativePtr, type, e.getMessage());
+ }
+
+ @Override
+ public void onResponse(Response response) throws IOException {
+ Log.d(LOG_TAG, "onResponse");
+
+ byte[] body;
+ try {
+ body = response.body().bytes();
+ } catch (IOException e) {
+ onFailure(null, e);
+ //throw e;
+ return;
+ } finally {
+ response.body().close();
+ }
+
+ nativeOnResponse(mNativePtr, response.code(), response.message(), response.header("ETag"), response.header("Last-Modified"), response.header("Cache-Control"), response.header("Expires"), body);
+ }
+ }
+
+ /*
+ * Application interceptor that logs the outgoing request and the incoming response.
+ * Based on https://github.com/square/okhttp/wiki/Interceptors
+ */
+
+ class LoggingInterceptor implements Interceptor {
+
+ private final static String LOG_TAG = "LoggingInterceptor";
+
+ @Override public Response intercept(Interceptor.Chain chain) throws IOException {
+ Request request = chain.request();
+
+ long t1 = System.nanoTime();
+ Log.i(LOG_TAG, String.format("Sending request %s on %s%n%s",
+ request.url(), chain.connection(), request.headers()));
+
+ Response response = chain.proceed(request);
+
+ long t2 = System.nanoTime();
+ Log.i(LOG_TAG, String.format("Received response for %s in %.1fms%n%s",
+ response.request().url(), (t2 - t1) / 1e6d, response.headers()));
+
+ return response;
+ }
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/package-info.java
new file mode 100644
index 0000000000..a5dbf30463
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Do not use this package. Used internally by the SDK.
+ */
+package com.mapbox.mapboxsdk.http;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/layers/CustomLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/layers/CustomLayer.java
new file mode 100644
index 0000000000..f6fb297dc6
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/layers/CustomLayer.java
@@ -0,0 +1,21 @@
+package com.mapbox.mapboxsdk.layers;
+
+public class CustomLayer {
+ public CustomLayer(String id,
+ long context,
+ long initializeFunction,
+ long renderFunction,
+ long deinitializeFunction) {
+ this.mID = id;
+ this.mContext = context;
+ this.mInitializeFunction = initializeFunction;
+ this.mRenderFunction = renderFunction;
+ this.mDeinitializeFunction = deinitializeFunction;
+ }
+
+ public String mID;
+ public long mContext;
+ public long mInitializeFunction;
+ public long mRenderFunction;
+ public long mDeinitializeFunction;
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/layers/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/layers/package-info.java
new file mode 100644
index 0000000000..fda1a493e2
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/layers/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * This package contains classes to add and manage layers in your map.
+ */
+package com.mapbox.mapboxsdk.layers;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationListener.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationListener.java
new file mode 100644
index 0000000000..c385820423
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationListener.java
@@ -0,0 +1,13 @@
+package com.mapbox.mapboxsdk.location;
+
+import android.location.Location;
+
+public interface LocationListener {
+
+ /**
+ * Callback method for receiving location updates from LocationServices.
+ * @param location The new Location data
+ */
+ public void onLocationChanged(Location location);
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationServices.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationServices.java
new file mode 100644
index 0000000000..74a68c3722
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationServices.java
@@ -0,0 +1,131 @@
+package com.mapbox.mapboxsdk.location;
+
+import android.content.Context;
+import android.location.Location;
+import android.support.annotation.NonNull;
+import com.mapzen.android.lost.api.LocationRequest;
+import com.mapzen.android.lost.api.LostApiClient;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LocationServices implements com.mapzen.android.lost.api.LocationListener {
+
+ private static LocationServices instance = null;
+
+ private LostApiClient mLocationClient;
+ private LocationRequest mLocationRequest;
+
+ private Location lastLocation = null;
+
+ private List<LocationListener> locationListeners = null;
+
+ private boolean isGPSEnabled = false;
+
+ /**
+ * Private constructor for singleton LocationServices
+ */
+ private LocationServices(Context context) {
+ super();
+ // Setup location services
+ mLocationClient = new LostApiClient.Builder(context).build();
+ locationListeners = new ArrayList<>();
+ }
+
+ /**
+ * Primary (singleton) access method for LocationServices
+ * @param context Context
+ * @return LocationServices
+ */
+ public static LocationServices getLocationServices(@NonNull final Context context) {
+ if (instance == null) {
+ if (context == null) {
+ throw new NullPointerException("Context required for accessing LocationServices");
+ }
+ instance = new LocationServices(context.getApplicationContext());
+ }
+ return instance;
+ }
+
+ /**
+ * Enabled / Disable GPS focused location tracking
+ *
+ * @param enableGPS true if GPS is to be enabled, false if GPS is to be disabled
+ */
+ public void toggleGPS(boolean enableGPS) {
+
+ if (enableGPS) {
+
+ if (mLocationClient.isConnected()) {
+ // Disconnect first to ensure that the new requests are GPS
+ com.mapzen.android.lost.api.LocationServices.FusedLocationApi.removeLocationUpdates(this);
+ mLocationClient.disconnect();
+ }
+
+ // Setup Fresh
+ mLocationClient.connect();
+ Location lastLocation = com.mapzen.android.lost.api.LocationServices.FusedLocationApi.getLastLocation();
+ if (lastLocation != null) {
+ this.lastLocation = lastLocation;
+ }
+
+ // LocationRequest Tuned for GPS
+ mLocationRequest = LocationRequest.create()
+ .setFastestInterval(1000)
+ .setSmallestDisplacement(3.0f)
+ .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
+
+ com.mapzen.android.lost.api.LocationServices.FusedLocationApi.requestLocationUpdates(mLocationRequest, this);
+
+ } else {
+
+ // Disconnect
+ if (mLocationClient.isConnected()) {
+ // Disconnect first to ensure that the new requests are GPS
+ com.mapzen.android.lost.api.LocationServices.FusedLocationApi.removeLocationUpdates(this);
+ mLocationClient.disconnect();
+ }
+
+ }
+
+ isGPSEnabled = enableGPS;
+ }
+
+ public boolean isGPSEnabled() {
+ return isGPSEnabled;
+ }
+
+ @Override
+ public void onLocationChanged(Location location) {
+ this.lastLocation = location;
+
+ // Update Listeners
+ for (LocationListener listener : this.locationListeners) {
+ listener.onLocationChanged(location);
+ }
+ }
+
+ /**
+ * Last known location
+ * @return Last known location data
+ */
+ public Location getLastLocation() {
+ return lastLocation;
+ }
+
+ /**
+ * Registers a LocationListener to receive location updates
+ * @param locationListener LocationListener
+ */
+ public void addLocationListener(@NonNull LocationListener locationListener) {
+ this.locationListeners.add(locationListener);
+ }
+
+ /**
+ * Unregister a LocationListener to stop receiving location updates
+ * @param locationListener LocationListener to remove
+ * @return True if LocationListener was found and removed, False if it was not
+ */
+ public boolean removeLocationListener(@NonNull LocationListener locationListener) {
+ return this.locationListeners.remove(locationListener);
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/package-info.java
new file mode 100644
index 0000000000..474a554463
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * This package contains the {@link com.mapbox.mapboxsdk.MapFragment} class. {@code MapFragment}
+ * provides a quick and easy way to add a map to your app.
+ */
+package com.mapbox.mapboxsdk;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ApiAccess.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ApiAccess.java
new file mode 100644
index 0000000000..c92031c548
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ApiAccess.java
@@ -0,0 +1,45 @@
+package com.mapbox.mapboxsdk.utils;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.mapbox.mapboxsdk.constants.MapboxConstants;
+
+/**
+ * {@code ApiAccess} provides a method to load the Mapbox access token.
+ */
+public final class ApiAccess {
+
+ /**
+ * <p>
+ * Returns the Mapbox access token set in the app resources.
+ * </p>
+ * It will first search the application manifest for a {@link MapboxConstants#KEY_META_DATA_MANIFEST}
+ * meta-data value. If not found it will then attempt to load the access token from the
+ * {@code res/raw/token.txt} development file.
+ *
+ * @param context The {@link Context} of the {@link android.app.Activity} or {@link android.app.Fragment}.
+ * @return The Mapbox access token or null if not found.
+ * @see MapboxConstants#KEY_META_DATA_MANIFEST
+ */
+ @Nullable
+ public static String getToken(@NonNull Context context) {
+ try {
+ // read out AndroidManifest
+ PackageManager packageManager = context.getPackageManager();
+ ApplicationInfo appInfo = packageManager.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
+ String token = appInfo.metaData.getString(MapboxConstants.KEY_META_DATA_MANIFEST);
+ if (token == null || token.isEmpty()) {
+ throw new IllegalArgumentException();
+ }
+ return token;
+ } catch (Exception e) {
+ // use fallback on string resource, used for development
+ int tokenResId = context.getResources().getIdentifier("access_token", "string", context.getPackageName());
+ return tokenResId != 0 ? context.getString(tokenResId) : null;
+ }
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/package-info.java
new file mode 100644
index 0000000000..9d9c0bf4bb
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * This package contains the {@link com.mapbox.mapboxsdk.utils.ApiAccess} class. {@code ApiAccess}
+ * provides a methods to load a Mapbox access token.
+ */
+package com.mapbox.mapboxsdk.utils;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/CompassView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/CompassView.java
new file mode 100644
index 0000000000..7bd07d0934
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/CompassView.java
@@ -0,0 +1,156 @@
+package com.mapbox.mapboxsdk.views;
+
+import android.content.Context;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.mapbox.mapboxsdk.R;
+
+import java.lang.ref.WeakReference;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * CompassView is a UI element overlaid on a map that shows the map's bearing
+ * when it isn't true north (0.0). Tapping the compass resets the bearing to true
+ * north and hides the compass.
+ */
+final class CompassView extends ImageView {
+
+ private Timer mNorthTimer;
+ private double mDirection = 0.0f;
+ private ViewPropertyAnimatorCompat mFadeAnimator;
+
+ public CompassView(Context context) {
+ super(context);
+ initialize(context);
+ }
+
+ public CompassView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize(context);
+ }
+
+ public CompassView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize(context);
+ }
+
+ private void initialize(Context context) {
+
+ // View configuration
+ setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.compass));
+ setContentDescription(getResources().getString(R.string.compassContentDescription));
+ setEnabled(false);
+
+ // Layout params
+ float mScreenDensity = context.getResources().getDisplayMetrics().density;
+ ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams((int) (48 * mScreenDensity), (int) (48 * mScreenDensity));
+ setLayoutParams(lp);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (enabled) {
+ if (mDirection != 0.0) {
+ if (mNorthTimer != null){
+ mNorthTimer.cancel();
+ mNorthTimer = null;
+ }
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+ mFadeAnimator = null;
+ setAlpha(1.0f);
+ setVisibility(View.VISIBLE);
+ }
+ } else {
+ if (mNorthTimer != null){
+ mNorthTimer.cancel();
+ mNorthTimer = null;
+ }
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+ mFadeAnimator = null;
+ setVisibility(View.INVISIBLE);
+ }
+ }
+
+ public void update(double direction) {
+ mDirection = direction;
+ setRotation((float) direction);
+
+ if (!isEnabled()) {
+ return;
+ }
+
+ if (direction == 0.0) {
+ if (getVisibility() == View.INVISIBLE) {
+ return;
+ }
+
+ if (mNorthTimer == null) {
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+ mFadeAnimator = null;
+
+ mNorthTimer = new Timer("CompassView North timer");
+ mNorthTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ setAlpha(1.0f);
+ mFadeAnimator = ViewCompat.animate(CompassView.this).alpha(0.0f).setDuration(1000).withLayer();
+ mFadeAnimator.setListener(new ViewPropertyAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(View view) {
+ setVisibility(View.INVISIBLE);
+ mNorthTimer = null;
+ }
+ });
+ }
+ });
+ }
+ }, 1000);
+ }
+ } else {
+ if (mNorthTimer != null){
+ mNorthTimer.cancel();
+ mNorthTimer = null;
+ }
+ if (mFadeAnimator != null) {
+ mFadeAnimator.cancel();
+ }
+ setAlpha(1.0f);
+ setVisibility(View.VISIBLE);
+ }
+ }
+
+ public static class CompassClickListener implements View.OnClickListener {
+
+ private WeakReference<MapView> mMapView;
+
+ public CompassClickListener(final MapView mapView) {
+ mMapView = new WeakReference<>(mapView);
+ }
+
+ @Override
+ public void onClick(View v) {
+ final MapView mapView = mMapView.get();
+ if (mapView != null) {
+ mapView.resetNorth();
+ }
+ }
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/MapView.java
new file mode 100644
index 0000000000..f169e98b86
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/MapView.java
@@ -0,0 +1,3846 @@
+package com.mapbox.mapboxsdk.views;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Dialog;
+import android.app.Fragment;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.graphics.SurfaceTexture;
+import android.location.Location;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.annotation.FloatRange;
+import android.support.annotation.IntDef;
+import android.support.annotation.IntRange;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+import android.support.v4.view.GestureDetectorCompat;
+import android.support.v4.view.ScaleGestureDetectorCompat;
+import android.support.v7.app.AlertDialog;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ZoomButtonsController;
+import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector;
+import com.almeros.android.multitouch.gesturedetectors.ShoveGestureDetector;
+import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector;
+import com.mapbox.mapboxsdk.R;
+import com.mapbox.mapboxsdk.annotations.Annotation;
+import com.mapbox.mapboxsdk.annotations.InfoWindow;
+import com.mapbox.mapboxsdk.annotations.Marker;
+import com.mapbox.mapboxsdk.annotations.MarkerOptions;
+import com.mapbox.mapboxsdk.annotations.Polygon;
+import com.mapbox.mapboxsdk.annotations.PolygonOptions;
+import com.mapbox.mapboxsdk.annotations.Polyline;
+import com.mapbox.mapboxsdk.annotations.PolylineOptions;
+import com.mapbox.mapboxsdk.annotations.Sprite;
+import com.mapbox.mapboxsdk.annotations.SpriteFactory;
+import com.mapbox.mapboxsdk.constants.MyBearingTracking;
+import com.mapbox.mapboxsdk.constants.MyLocationTracking;
+import com.mapbox.mapboxsdk.constants.Style;
+import com.mapbox.mapboxsdk.exceptions.InvalidAccessTokenException;
+import com.mapbox.mapboxsdk.exceptions.SpriteBitmapChangedException;
+import com.mapbox.mapboxsdk.geometry.BoundingBox;
+import com.mapbox.mapboxsdk.geometry.CoordinateBounds;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.geometry.LatLngZoom;
+import com.mapbox.mapboxsdk.layers.CustomLayer;
+import com.mapbox.mapboxsdk.utils.ApiAccess;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * <p>
+ * A {@code MapView} provides an embeddable map interface.
+ * You use this class to display map information and to manipulate the map contents from your application.
+ * You can center the map on a given coordinate, specify the size of the area you want to display,
+ * and style the features of the map to fit your application's use case.
+ * </p>
+ * <p>
+ * Use of {@code MapView} requires a Mapbox API access token.
+ * Obtain an access token on the <a href="https://www.mapbox.com/studio/account/tokens/">Mapbox account page</a>.
+ * </p>
+ * <strong>Warning:</strong> Please note that you are responsible for getting permission to use the map data,
+ * and for ensuring your use adheres to the relevant terms of use.
+ *
+ * @see MapView#setAccessToken(String)
+ */
+public final class MapView extends FrameLayout {
+
+ //
+ // Static members
+ //
+
+ // Used for logging
+ private static final String TAG = "MapView";
+
+ // Used for animation
+ private static final long ANIMATION_DURATION = 300;
+
+ // Used for saving instance state
+ private static final String STATE_CENTER_COORDINATE = "centerCoordinate";
+ private static final String STATE_CENTER_DIRECTION = "centerDirection";
+ private static final String STATE_ZOOM_LEVEL = "zoomLevel";
+ private static final String STATE_TILT = "tilt";
+ private static final String STATE_ZOOM_ENABLED = "zoomEnabled";
+ private static final String STATE_SCROLL_ENABLED = "scrollEnabled";
+ private static final String STATE_ROTATE_ENABLED = "rotateEnabled";
+ private static final String STATE_TILT_ENABLED = "tiltEnabled";
+ private static final String STATE_ZOOM_CONTROLS_ENABLED = "zoomControlsEnabled";
+ private static final String STATE_DEBUG_ACTIVE = "debugActive";
+ private static final String STATE_STYLE_URL = "styleUrl";
+ private static final String STATE_ACCESS_TOKEN = "accessToken";
+ private static final String STATE_STYLE_CLASSES = "styleClasses";
+ private static final String STATE_DEFAULT_TRANSITION_DURATION = "defaultTransitionDuration";
+ private static final String STATE_MY_LOCATION_ENABLED = "myLocationEnabled";
+ private static final String STATE_MY_LOCATION_TRACKING_MODE = "myLocationTracking";
+ private static final String STATE_COMPASS_ENABLED = "compassEnabled";
+ private static final String STATE_COMPASS_GRAVITY = "compassGravity";
+ private static final String STATE_COMPASS_MARGIN_LEFT = "compassMarginLeft";
+ private static final String STATE_COMPASS_MARGIN_TOP = "compassMarginTop";
+ private static final String STATE_COMPASS_MARGIN_RIGHT = "compassMarginRight";
+ private static final String STATE_COMPASS_MARGIN_BOTTOM = "compassMarginBottom";
+ private static final String STATE_LOGO_GRAVITY = "logoGravity";
+ private static final String STATE_LOGO_MARGIN_LEFT = "logoMarginLeft";
+ private static final String STATE_LOGO_MARGIN_TOP = "logoMarginTop";
+ private static final String STATE_LOGO_MARGIN_RIGHT = "logoMarginRight";
+ private static final String STATE_LOGO_MARGIN_BOTTOM = "logoMarginBottom";
+ private static final String STATE_LOGO_VISIBILITY = "logoVisibility";
+ private static final String STATE_ATTRIBUTION_GRAVITY = "attrGravity";
+ private static final String STATE_ATTRIBUTION_MARGIN_LEFT = "attrMarginLeft";
+ private static final String STATE_ATTRIBUTION_MARGIN_TOP = "attrMarginTop";
+ private static final String STATE_ATTRIBUTION_MARGIN_RIGHT = "attrMarginRight";
+ private static final String STATE_ATTRIBUTION_MARGIN_BOTTOM = "atrrMarginBottom";
+ private static final String STATE_ATTRIBUTION_VISIBILITY = "atrrVisibility";
+
+ // Used for positioning views
+ private static final float DIMENSION_SEVEN_DP = 7f;
+ private static final float DIMENSION_TEN_DP = 10f;
+ private static final float DIMENSION_SIXTEEN_DP = 16f;
+ private static final float DIMENSION_SEVENTYSIX_DP = 76f;
+
+ // Used to select "Improve this map" link in the attribution dialog
+ // Index into R.arrays.attribution_links
+ private static final int ATTRIBUTION_INDEX_IMPROVE_THIS_MAP = 2;
+
+ /**
+ * The currently supported maximum zoom level.
+ *
+ * @see MapView#setZoomLevel(double)
+ */
+ public static final double MAXIMUM_ZOOM_LEVEL = 18.0;
+
+ /**
+ * The currently supported maximum and minimum tilt values.
+ *
+ * @see MapView#setTilt(double)
+ */
+ private static final double MINIMUM_TILT = 0;
+ private static final double MAXIMUM_TILT = 60;
+
+ //
+ // Instance members
+ //
+
+ // Used to call JNI NativeMapView
+ private NativeMapView mNativeMapView;
+
+ // Used to track rendering
+ private TextureView mTextureView;
+
+ // Used to handle DPI scaling
+ private float mScreenDensity = 1.0f;
+
+ // Touch gesture detectors
+ private GestureDetectorCompat mGestureDetector;
+ private ScaleGestureDetector mScaleGestureDetector;
+ private RotateGestureDetector mRotateGestureDetector;
+ private ShoveGestureDetector mShoveGestureDetector;
+ private boolean mTwoTap = false;
+ private boolean mZoomStarted = false;
+ private boolean mQuickZoom = false;
+
+ // Shows zoom buttons
+ private ZoomButtonsController mZoomButtonsController;
+ private boolean mZoomControlsEnabled = false;
+
+ // Used to track trackball long presses
+ private TrackballLongPressTimeOut mCurrentTrackballLongPressTimeOut;
+
+ // Receives changes to network connectivity
+ private ConnectivityReceiver mConnectivityReceiver;
+
+ // Used for user location
+ private UserLocationView mUserLocationView;
+
+ // Used for the compass
+ private CompassView mCompassView;
+
+ // Used for displaying annotations
+ // Every annotation that has been added to the map
+ private final List<Annotation> mAnnotations = new ArrayList<>();
+ private List<Marker> mMarkersNearLastTap = new ArrayList<>();
+ private List<Marker> mSelectedMarkers = new ArrayList<>();
+ private List<InfoWindow> mInfoWindows = new ArrayList<>();
+ private InfoWindowAdapter mInfoWindowAdapter;
+ private SpriteFactory mSpriteFactory;
+ private ArrayList<Sprite> mSprites = new ArrayList<>();
+
+ // Used for the Mapbox Logo
+ private ImageView mLogoView;
+
+ // Used for attributions control
+ private ImageView mAttributionsView;
+
+ // Used to manage MapChange event listeners
+ private ArrayList<OnMapChangedListener> mOnMapChangedListener = new ArrayList<>();
+
+ // Used to manage map click event listeners
+ private OnMapClickListener mOnMapClickListener;
+ private OnMapLongClickListener mOnMapLongClickListener;
+
+ // Used to manage fling and scroll event listeners
+ private OnFlingListener mOnFlingListener;
+ private OnScrollListener mOnScrollListener;
+
+ // Used to manage marker click event listeners
+ private OnMarkerClickListener mOnMarkerClickListener;
+ private OnInfoWindowClickListener mOnInfoWindowClickListener;
+
+ // Used to manage FPS change event listeners
+ private OnFpsChangedListener mOnFpsChangedListener;
+
+ //
+ // Properties
+ //
+
+ // These are properties with setters/getters, saved in onSaveInstanceState and XML attributes
+ private boolean mZoomEnabled = true;
+ private boolean mScrollEnabled = true;
+ private boolean mRotateEnabled = true;
+ private boolean mTiltEnabled = true;
+ private boolean mAllowConcurrentMultipleOpenInfoWindows = false;
+ private String mStyleUrl;
+
+ //
+ // Inner classes
+ //
+
+ //
+ // Enums
+ //
+
+ /**
+ * Map change event types.
+ *
+ * @see MapView.OnMapChangedListener#onMapChanged(int)
+ */
+ @IntDef({REGION_WILL_CHANGE,
+ REGION_WILL_CHANGE_ANIMATED,
+ REGION_IS_CHANGING,
+ REGION_DID_CHANGE,
+ REGION_DID_CHANGE_ANIMATED,
+ WILL_START_LOADING_MAP,
+ DID_FINISH_LOADING_MAP,
+ DID_FAIL_LOADING_MAP,
+ WILL_START_RENDERING_FRAME,
+ DID_FINISH_RENDERING_FRAME,
+ DID_FINISH_RENDERING_FRAME_FULLY_RENDERED,
+ WILL_START_RENDERING_MAP,
+ DID_FINISH_RENDERING_MAP,
+ DID_FINISH_RENDERING_MAP_FULLY_RENDERED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MapChange {
+ }
+
+ /**
+ * <p>
+ * This {@link MapChange} is triggered whenever the currently displayed map region is about to changing
+ * without an animation.
+ * </p>
+ * <p>
+ * This event is followed by a series of {@link MapView#REGION_IS_CHANGING} and ends
+ * with {@link MapView#REGION_DID_CHANGE}.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int REGION_WILL_CHANGE = 0;
+
+ /**
+ * <p>
+ * This {@link MapChange} is triggered whenever the currently displayed map region is about to changing
+ * with an animation.
+ * </p>
+ * <p>
+ * This event is followed by a series of {@link MapView#REGION_IS_CHANGING} and ends
+ * with {@link MapView#REGION_DID_CHANGE_ANIMATED}.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int REGION_WILL_CHANGE_ANIMATED = 1;
+
+ /**
+ * <p>
+ * This {@link MapChange} is triggered whenever the currently displayed map region is changing.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int REGION_IS_CHANGING = 2;
+
+ /**
+ * <p>
+ * This {@link MapChange} is triggered whenever the currently displayed map region finished changing
+ * without an animation.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int REGION_DID_CHANGE = 3;
+
+ /**
+ * <p>
+ * This {@link MapChange} is triggered whenever the currently displayed map region finished changing
+ * with an animation.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int REGION_DID_CHANGE_ANIMATED = 4;
+
+ /**
+ * <p>
+ * This {@link MapChange} is triggered when the map is about to start loading a new map style.
+ * </p>
+ * <p>
+ * This event is followed by {@link MapView#DID_FINISH_LOADING_MAP} or
+ * {@link MapView#DID_FAIL_LOADING_MAP}.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int WILL_START_LOADING_MAP = 5;
+
+ /**
+ * <p>
+ * This {@link MapChange} is triggered when the map has successfully loaded a new map style.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int DID_FINISH_LOADING_MAP = 6;
+
+ /**
+ * <p>
+ * This {@link MapChange} is currently not implemented.
+ * </p>
+ * <p>
+ * This event is triggered when the map has failed to load a new map style.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int DID_FAIL_LOADING_MAP = 7;
+
+ /**
+ * <p>
+ * This {@link MapChange} is currently not implemented.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int WILL_START_RENDERING_FRAME = 8;
+
+ /**
+ * <p>
+ * This {@link MapChange} is currently not implemented.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int DID_FINISH_RENDERING_FRAME = 9;
+
+ /**
+ * <p>
+ * This {@link MapChange} is currently not implemented.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int DID_FINISH_RENDERING_FRAME_FULLY_RENDERED = 10;
+
+ /**
+ * <p>
+ * This {@link MapChange} is currently not implemented.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int WILL_START_RENDERING_MAP = 11;
+
+ /**
+ * <p>
+ * This {@link MapChange} is currently not implemented.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int DID_FINISH_RENDERING_MAP = 12;
+
+ /**
+ * <p>
+ * This {@link MapChange} is currently not implemented.
+ * </p>
+ * @see com.mapbox.mapboxsdk.views.MapView.OnMapChangedListener
+ */
+ public static final int DID_FINISH_RENDERING_MAP_FULLY_RENDERED = 13;
+
+ //
+ // Interfaces
+ //
+
+ /**
+ * Interface definition for a callback to be invoked when the map is flinged.
+ *
+ * @see MapView#setOnFlingListener(OnFlingListener)
+ */
+ public interface OnFlingListener {
+ /**
+ * Called when the map is flinged.
+ */
+ void onFling();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the map is scrolled.
+ *
+ * @see MapView#setOnScrollListener(OnScrollListener)
+ */
+ public interface OnScrollListener {
+ /**
+ * Called when the map is scrolled.
+ */
+ void onScroll();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked on every frame rendered to the map view.
+ *
+ * @see MapView#setOnFpsChangedListener(OnFpsChangedListener)
+ */
+ public interface OnFpsChangedListener {
+ /**
+ * Called for every frame rendered to the map view.
+ *
+ * @param fps The average number of frames rendered over the last second.
+ */
+ void onFpsChanged(double fps);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the user clicks on the map view.
+ *
+ * @see MapView#setOnMapClickListener(OnMapClickListener)
+ */
+ public interface OnMapClickListener {
+ /**
+ * Called when the user clicks on the map view.
+ *
+ * @param point The projected map coordinate the user clicked on.
+ */
+ void onMapClick(@NonNull LatLng point);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the user long clicks on the map view.
+ *
+ * @see MapView#setOnMapLongClickListener(OnMapLongClickListener)
+ */
+ public interface OnMapLongClickListener {
+ /**
+ * Called when the user long clicks on the map view.
+ *
+ * @param point The projected map coordinate the user long clicked on.
+ */
+ void onMapLongClick(@NonNull LatLng point);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the user clicks on a marker.
+ *
+ * @see MapView#setOnMarkerClickListener(OnMarkerClickListener)
+ */
+ public interface OnMarkerClickListener {
+ /**
+ * Called when the user clicks on a marker.
+ *
+ * @param marker The marker the user clicked on.
+ * @return If true the listener has consumed the event and the info window will not be shown.
+ */
+ boolean onMarkerClick(@NonNull Marker marker);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the user clicks on an info window.
+ *
+ * @see MapView#setOnInfoWindowClickListener(OnInfoWindowClickListener)
+ */
+ public interface OnInfoWindowClickListener {
+ /**
+ * Called when the user clicks on an info window.
+ *
+ * @param marker The marker of the info window the user clicked on.
+ * @return If true the listener has consumed the event and the info window will not be closed.
+ */
+ boolean onMarkerClick(@NonNull Marker marker);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the displayed map view changes.
+ *
+ * @see MapView#addOnMapChangedListener(OnMapChangedListener)
+ * @see MapView.MapChange
+ */
+ public interface OnMapChangedListener {
+ /**
+ * Called when the displayed map view changes.
+ *
+ * @param change Type of map change event, one of {@link #REGION_WILL_CHANGE},
+ * {@link #REGION_WILL_CHANGE_ANIMATED},
+ * {@link #REGION_IS_CHANGING},
+ * {@link #REGION_DID_CHANGE},
+ * {@link #REGION_DID_CHANGE_ANIMATED},
+ * {@link #WILL_START_LOADING_MAP},
+ * {@link #DID_FAIL_LOADING_MAP},
+ * {@link #DID_FINISH_LOADING_MAP},
+ * {@link #WILL_START_RENDERING_FRAME},
+ * {@link #DID_FINISH_RENDERING_FRAME},
+ * {@link #DID_FINISH_RENDERING_FRAME_FULLY_RENDERED},
+ * {@link #WILL_START_RENDERING_MAP},
+ * {@link #DID_FINISH_RENDERING_MAP},
+ * {@link #DID_FINISH_RENDERING_MAP_FULLY_RENDERED}.
+ */
+ void onMapChanged(@MapChange int change);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when an info window will be shown.
+ *
+ * @see MapView#setInfoWindowAdapter(InfoWindowAdapter)
+ */
+ public interface InfoWindowAdapter {
+ /**
+ * Called when an info window will be shown as a result of a marker click.
+ *
+ * @param marker The marker the user clicked on.
+ * @return View to be shown as a info window. If null is returned the default
+ * info window will be shown.
+ */
+ @Nullable
+ View getInfoWindow(@NonNull Marker marker);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the the My Location dot
+ * (which signifies the user's location) changes location.
+ *
+ * @see MapView#setOnMyLocationChangeListener(OnMyLocationChangeListener)
+ */
+ public interface OnMyLocationChangeListener {
+ /**
+ * Called when the location of the My Location dot has changed
+ * (be it latitude/longitude, bearing or accuracy).
+ *
+ * @param location The current location of the My Location dot The type of map change event.
+ */
+ void onMyLocationChange(@Nullable Location location);
+ }
+
+ //
+ // Constructors
+ //
+
+ /**
+ * Simple constructor to use when creating a {@link MapView} from code using the default map style.
+ *
+ * @param context The {@link Context} of the {@link android.app.Activity}
+ * or {@link android.app.Fragment} the {@link MapView} is running in.
+ * @param accessToken Your public Mapbox access token. Used to load map styles and tiles.
+ */
+ @UiThread
+ public MapView(@NonNull Context context, @NonNull String accessToken) {
+ super(context);
+ if (accessToken == null) {
+ Log.w(TAG, "accessToken was null, so just returning");
+ return;
+ }
+ initialize(context, null);
+ setAccessToken(accessToken);
+ setStyleUrl(null);
+ }
+
+ /**
+ * Simple constructor to use when creating a {@link MapView} from code using the provided map style URL.
+ *
+ * @param context The {@link Context} of the {@link android.app.Activity}
+ * or {@link android.app.Fragment} the {@link MapView} is running in.
+ * @param accessToken Your public Mapbox access token. Used to load map styles and tiles.
+ * @param styleUrl A URL to the map style initially displayed. See {@link MapView#setStyleUrl(String)} for possible values.
+ * @see MapView#setStyleUrl(String)
+ */
+ @UiThread
+ public MapView(@NonNull Context context, @NonNull String accessToken, @NonNull String styleUrl) {
+ super(context);
+ if (accessToken == null) {
+ Log.w(TAG, "accessToken was null, so just returning");
+ return;
+ }
+ if (styleUrl == null) {
+ Log.w(TAG, "styleUrl was null, so just returning");
+ return;
+ }
+ initialize(context, null);
+ setAccessToken(accessToken);
+ setStyleUrl(styleUrl);
+ }
+
+ // Constructor that is called when inflating a view from XML.
+
+ /**
+ * Do not call from code.
+ */
+ @UiThread
+ public MapView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ initialize(context, attrs);
+ }
+
+ // Constructor that is called when inflating a view from XML.
+
+ /**
+ * Do not call from code.
+ */
+ @UiThread
+ public MapView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize(context, attrs);
+ }
+
+ //
+ // Initialization
+ //
+
+ // Common initialization code goes here
+ private void initialize(Context context, AttributeSet attrs) {
+ if (context == null) {
+ Log.w(TAG, "context was null, so just returning");
+ return;
+ }
+
+ // Inflate content
+ View view = LayoutInflater.from(context).inflate(R.layout.mapview_internal, this);
+
+ if (!isInEditMode()) {
+ setWillNotDraw(false);
+ }
+
+ // Reference the TextureView
+ mTextureView = (TextureView) view.findViewById(R.id.textureView);
+ mTextureView.setSurfaceTextureListener(new SurfaceTextureListener());
+
+ // Check if we are in Android Studio UI editor to avoid error in layout preview
+ if (isInEditMode()) {
+ return;
+ }
+
+ // Get the screen's density
+ mScreenDensity = context.getResources().getDisplayMetrics().density;
+
+ // Get the cache path
+ String cachePath = context.getCacheDir().getAbsolutePath();
+ String dataPath = context.getFilesDir().getAbsolutePath();
+ String apkPath = context.getPackageCodePath();
+
+ // Create the NativeMapView
+ int availableProcessors = Runtime.getRuntime().availableProcessors();
+ ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
+ ActivityManager activityManager = (ActivityManager) context
+ .getSystemService(Context.ACTIVITY_SERVICE);
+ activityManager.getMemoryInfo(memoryInfo);
+ long maxMemory = memoryInfo.availMem;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ maxMemory = memoryInfo.totalMem;
+ }
+ mNativeMapView = new
+ NativeMapView(this, cachePath, dataPath, apkPath, mScreenDensity, availableProcessors, maxMemory);
+
+ // Ensure this view is interactable
+ setClickable(true);
+ setLongClickable(true);
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ requestFocus();
+
+ // Touch gesture detectors
+ mGestureDetector = new GestureDetectorCompat(context, new GestureListener());
+ mGestureDetector.setIsLongpressEnabled(true);
+ mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
+ ScaleGestureDetectorCompat.setQuickScaleEnabled(mScaleGestureDetector, true);
+ mRotateGestureDetector = new RotateGestureDetector(context, new RotateGestureListener());
+ mShoveGestureDetector = new ShoveGestureDetector(context, new ShoveGestureListener());
+
+ // Shows the zoom controls
+ if (!context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) {
+ mZoomControlsEnabled = true;
+ }
+ mZoomButtonsController = new ZoomButtonsController(this);
+ mZoomButtonsController.setZoomSpeed(ANIMATION_DURATION);
+ mZoomButtonsController.setOnZoomListener(new OnZoomListener());
+
+ // Check current connection status
+ ConnectivityManager connectivityManager = (ConnectivityManager) context
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
+ boolean isConnected = (activeNetwork != null) && activeNetwork.isConnectedOrConnecting();
+ onConnectivityChanged(isConnected);
+
+ // Setup user location UI
+ mUserLocationView = (UserLocationView) view.findViewById(R.id.userLocationView);
+ mUserLocationView.setMapView(this);
+
+ // Setup compass
+ mCompassView = (CompassView) view.findViewById(R.id.compassView);
+ mCompassView.setOnClickListener(new CompassView.CompassClickListener(this));
+
+ // Setup Mapbox logo
+ mLogoView = (ImageView) view.findViewById(R.id.logoView);
+
+ // Setup Attributions control
+ mAttributionsView = (ImageView) view.findViewById(R.id.attributionView);
+ mAttributionsView.setOnClickListener(new AttributionOnClickListener(this));
+
+ // Load the attributes
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView, 0, 0);
+ try {
+ double centerLatitude = typedArray.getFloat(R.styleable.MapView_center_latitude, 0.0f);
+ double centerLongitude = typedArray.getFloat(R.styleable.MapView_center_longitude, 0.0f);
+ LatLng centerCoordinate = new LatLng(centerLatitude, centerLongitude);
+ setCenterCoordinate(centerCoordinate);
+ // need to set zoom level first because of limitation on rotating when zoomed out
+ setZoomLevel(typedArray.getFloat(R.styleable.MapView_zoom_level, 0.0f));
+ setDirection(typedArray.getFloat(R.styleable.MapView_direction, 0.0f));
+ setZoomEnabled(typedArray.getBoolean(R.styleable.MapView_zoom_enabled, true));
+ setScrollEnabled(typedArray.getBoolean(R.styleable.MapView_scroll_enabled, true));
+ setRotateEnabled(typedArray.getBoolean(R.styleable.MapView_rotate_enabled, true));
+ setTiltEnabled(typedArray.getBoolean(R.styleable.MapView_tilt_enabled, true));
+ setZoomControlsEnabled(typedArray.getBoolean(R.styleable.MapView_zoom_controls_enabled, isZoomControlsEnabled()));
+ setDebugActive(typedArray.getBoolean(R.styleable.MapView_debug_active, false));
+ if (typedArray.getString(R.styleable.MapView_style_url) != null) {
+ setStyleUrl(typedArray.getString(R.styleable.MapView_style_url));
+ }
+ if (typedArray.getString(R.styleable.MapView_access_token) != null) {
+ setAccessToken(typedArray.getString(R.styleable.MapView_access_token));
+ }
+ if (typedArray.getString(R.styleable.MapView_style_classes) != null) {
+ List<String> styleClasses = Arrays.asList(typedArray
+ .getString(R.styleable.MapView_style_classes).split("\\s*,\\s*"));
+ for (String styleClass : styleClasses) {
+ if (styleClass.length() == 0) {
+ styleClasses.remove(styleClass);
+ }
+ }
+ setStyleClasses(styleClasses);
+ }
+
+ // Compass
+ setCompassEnabled(typedArray.getBoolean(R.styleable.MapView_compass_enabled, true));
+ setCompassGravity(typedArray.getInt(R.styleable.MapView_compass_gravity, Gravity.TOP | Gravity.END));
+ setWidgetMargins(mCompassView, typedArray.getDimension(R.styleable.MapView_compass_margin_left, DIMENSION_TEN_DP)
+ , typedArray.getDimension(R.styleable.MapView_compass_margin_top, DIMENSION_TEN_DP)
+ , typedArray.getDimension(R.styleable.MapView_compass_margin_right, DIMENSION_TEN_DP)
+ , typedArray.getDimension(R.styleable.MapView_compass_margin_bottom, DIMENSION_TEN_DP));
+
+ // Logo
+ setLogoVisibility(typedArray.getInt(R.styleable.MapView_logo_visibility, View.VISIBLE));
+ setLogoGravity(typedArray.getInt(R.styleable.MapView_logo_gravity, Gravity.BOTTOM | Gravity.START));
+ setWidgetMargins(mLogoView, typedArray.getDimension(R.styleable.MapView_logo_margin_left, DIMENSION_SIXTEEN_DP)
+ , typedArray.getDimension(R.styleable.MapView_logo_margin_top, DIMENSION_SIXTEEN_DP)
+ , typedArray.getDimension(R.styleable.MapView_logo_margin_right, DIMENSION_SIXTEEN_DP)
+ , typedArray.getDimension(R.styleable.MapView_logo_margin_bottom, DIMENSION_SIXTEEN_DP));
+
+ // Attribution
+ setAttributionVisibility(typedArray.getInt(R.styleable.MapView_attribution_visibility, View.VISIBLE));
+ setAttributionGravity(typedArray.getInt(R.styleable.MapView_attribution_gravity, Gravity.BOTTOM));
+ setWidgetMargins(mAttributionsView, typedArray.getDimension(R.styleable.MapView_attribution_margin_left, DIMENSION_SEVENTYSIX_DP)
+ , typedArray.getDimension(R.styleable.MapView_attribution_margin_top, DIMENSION_SEVEN_DP)
+ , typedArray.getDimension(R.styleable.MapView_attribution_margin_right, DIMENSION_SEVEN_DP)
+ , typedArray.getDimension(R.styleable.MapView_attribution_margin_bottom, DIMENSION_SEVEN_DP));
+
+ // User location
+ setMyLocationEnabled(typedArray.getBoolean(R.styleable.MapView_my_location_enabled, false));
+ } finally {
+ typedArray.recycle();
+ }
+ }
+
+ //
+ // Lifecycle events
+ //
+
+ /**
+ * <p>
+ * You must call this method from the parent's {@link android.app.Activity#onCreate(Bundle)} or
+ * {@link android.app.Fragment#onCreate(Bundle)}.
+ * </p>
+ * You must set a valid access token with {@link MapView#setAccessToken(String)} before you this method
+ * or an exception will be thrown.
+ *
+ * @param savedInstanceState Pass in the parent's savedInstanceState.
+ * @see MapView#setAccessToken(String)
+ */
+ @UiThread
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ setCenterCoordinate((LatLng) savedInstanceState.getParcelable(STATE_CENTER_COORDINATE));
+ // need to set zoom level first because of limitation on rotating when zoomed out
+ setZoomLevel(savedInstanceState.getDouble(STATE_ZOOM_LEVEL));
+ setDirection(savedInstanceState.getDouble(STATE_CENTER_DIRECTION));
+ setTilt(savedInstanceState.getDouble(STATE_TILT), null);
+ setZoomEnabled(savedInstanceState.getBoolean(STATE_ZOOM_ENABLED));
+ setScrollEnabled(savedInstanceState.getBoolean(STATE_SCROLL_ENABLED));
+ setRotateEnabled(savedInstanceState.getBoolean(STATE_ROTATE_ENABLED));
+ setTiltEnabled(savedInstanceState.getBoolean(STATE_TILT_ENABLED));
+ setZoomControlsEnabled(savedInstanceState.getBoolean(STATE_ZOOM_CONTROLS_ENABLED));
+ setDebugActive(savedInstanceState.getBoolean(STATE_DEBUG_ACTIVE));
+ setStyleUrl(savedInstanceState.getString(STATE_STYLE_URL));
+ setAccessToken(savedInstanceState.getString(STATE_ACCESS_TOKEN));
+ List<String> appliedStyleClasses = savedInstanceState.getStringArrayList(STATE_STYLE_CLASSES);
+ if (!appliedStyleClasses.isEmpty()) {
+ setStyleClasses(appliedStyleClasses);
+ }
+ mNativeMapView.setDefaultTransitionDuration(
+ savedInstanceState.getLong(STATE_DEFAULT_TRANSITION_DURATION));
+ setMyLocationEnabled(savedInstanceState.getBoolean(STATE_MY_LOCATION_ENABLED));
+
+ // Compass
+ setCompassEnabled(savedInstanceState.getBoolean(STATE_COMPASS_ENABLED));
+ setCompassGravity(savedInstanceState.getInt(STATE_COMPASS_GRAVITY));
+ setCompassMargins(savedInstanceState.getInt(STATE_COMPASS_MARGIN_LEFT)
+ , savedInstanceState.getInt(STATE_COMPASS_MARGIN_TOP)
+ , savedInstanceState.getInt(STATE_COMPASS_MARGIN_RIGHT)
+ , savedInstanceState.getInt(STATE_COMPASS_MARGIN_BOTTOM));
+
+ // Logo
+ setLogoVisibility(savedInstanceState.getInt(STATE_LOGO_VISIBILITY));
+ setLogoGravity(savedInstanceState.getInt(STATE_LOGO_GRAVITY));
+ setLogoMargins(savedInstanceState.getInt(STATE_LOGO_MARGIN_LEFT)
+ , savedInstanceState.getInt(STATE_LOGO_MARGIN_TOP)
+ , savedInstanceState.getInt(STATE_LOGO_MARGIN_RIGHT)
+ , savedInstanceState.getInt(STATE_LOGO_MARGIN_BOTTOM));
+
+ // Attribution
+ setAttributionVisibility(savedInstanceState.getInt(STATE_ATTRIBUTION_VISIBILITY));
+ setAttributionGravity(savedInstanceState.getInt(STATE_ATTRIBUTION_GRAVITY));
+ setAttributionMargins(savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_LEFT)
+ , savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_TOP)
+ , savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_RIGHT)
+ , savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_BOTTOM));
+
+ //noinspection ResourceType
+ setMyLocationTrackingMode(savedInstanceState.getInt(STATE_MY_LOCATION_TRACKING_MODE, MyLocationTracking.TRACKING_NONE));
+ }
+
+ // Force a check for an access token
+ validateAccessToken(getAccessToken());
+
+ // Initialize EGL
+ mNativeMapView.initializeDisplay();
+ mNativeMapView.initializeContext();
+
+ // Add annotation deselection listener
+ addOnMapChangedListener(new OnMapChangedListener() {
+ @Override
+ public void onMapChanged(@MapChange int change) {
+ if (change == DID_FINISH_LOADING_MAP) {
+ reloadSprites();
+ reloadMarkers();
+ adjustTopOffsetPixels();
+ }
+ }
+ });
+ }
+
+ /**
+ * You must call this method from the parent's {@link android.app.Activity#onSaveInstanceState(Bundle)}
+ * or {@link android.app.Fragment#onSaveInstanceState(Bundle)}.
+ *
+ * @param outState Pass in the parent's outState.
+ */
+ @UiThread
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ if (outState == null) {
+ Log.w(TAG, "outState was null, so just returning");
+ return;
+ }
+
+ outState.putParcelable(STATE_CENTER_COORDINATE, getCenterCoordinate());
+ // need to set zoom level first because of limitation on rotating when zoomed out
+ outState.putDouble(STATE_ZOOM_LEVEL, getZoomLevel());
+ outState.putDouble(STATE_CENTER_DIRECTION, getDirection());
+ outState.putDouble(STATE_TILT, getTilt());
+ outState.putBoolean(STATE_ZOOM_ENABLED, isZoomEnabled());
+ outState.putBoolean(STATE_SCROLL_ENABLED, isScrollEnabled());
+ outState.putBoolean(STATE_ROTATE_ENABLED, isRotateEnabled());
+ outState.putBoolean(STATE_TILT_ENABLED, isTiltEnabled());
+ outState.putBoolean(STATE_ZOOM_CONTROLS_ENABLED, isZoomControlsEnabled());
+ outState.putBoolean(STATE_DEBUG_ACTIVE, isDebugActive());
+ outState.putString(STATE_STYLE_URL, getStyleUrl());
+ outState.putString(STATE_ACCESS_TOKEN, getAccessToken());
+ outState.putStringArrayList(STATE_STYLE_CLASSES, new ArrayList<>(getStyleClasses()));
+ outState.putLong(STATE_DEFAULT_TRANSITION_DURATION, mNativeMapView.getDefaultTransitionDuration());
+ outState.putBoolean(STATE_MY_LOCATION_ENABLED, isMyLocationEnabled());
+ outState.putInt(STATE_MY_LOCATION_TRACKING_MODE, mUserLocationView.getMyLocationTrackingMode());
+
+ // Compass
+ LayoutParams compassParams = (LayoutParams) mCompassView.getLayoutParams();
+ outState.putBoolean(STATE_COMPASS_ENABLED, isCompassEnabled());
+ outState.putInt(STATE_COMPASS_GRAVITY, compassParams.gravity);
+ outState.putInt(STATE_COMPASS_MARGIN_LEFT, compassParams.leftMargin);
+ outState.putInt(STATE_COMPASS_MARGIN_TOP, compassParams.topMargin);
+ outState.putInt(STATE_COMPASS_MARGIN_BOTTOM, compassParams.bottomMargin);
+ outState.putInt(STATE_COMPASS_MARGIN_RIGHT, compassParams.rightMargin);
+
+ // Logo
+ LayoutParams logoParams = (LayoutParams) mLogoView.getLayoutParams();
+ outState.putInt(STATE_LOGO_GRAVITY, logoParams.gravity);
+ outState.putInt(STATE_LOGO_MARGIN_LEFT, logoParams.leftMargin);
+ outState.putInt(STATE_LOGO_MARGIN_TOP, logoParams.topMargin);
+ outState.putInt(STATE_LOGO_MARGIN_RIGHT, logoParams.rightMargin);
+ outState.putInt(STATE_LOGO_MARGIN_BOTTOM, logoParams.bottomMargin);
+ outState.putInt(STATE_LOGO_VISIBILITY, mLogoView.getVisibility());
+
+ // Attribution
+ LayoutParams attrParams = (LayoutParams) mAttributionsView.getLayoutParams();
+ outState.putInt(STATE_ATTRIBUTION_GRAVITY, attrParams.gravity);
+ outState.putInt(STATE_ATTRIBUTION_MARGIN_LEFT, attrParams.leftMargin);
+ outState.putInt(STATE_ATTRIBUTION_MARGIN_TOP, attrParams.topMargin);
+ outState.putInt(STATE_ATTRIBUTION_MARGIN_RIGHT, attrParams.rightMargin);
+ outState.putInt(STATE_ATTRIBUTION_MARGIN_BOTTOM, attrParams.bottomMargin);
+ outState.putInt(STATE_ATTRIBUTION_VISIBILITY, mAttributionsView.getVisibility());
+ }
+
+ /**
+ * You must call this method from the parent's {@link Activity#onDestroy()} or {@link Fragment#onDestroy()}.
+ */
+ @UiThread
+ public void onDestroy() {
+ mNativeMapView.terminateContext();
+ mNativeMapView.terminateDisplay();
+ mNativeMapView.destroySurface();
+ mNativeMapView.destroy();
+ mNativeMapView = null;
+ }
+
+ /**
+ * You must call this method from the parent's {@link Activity#onStart()} or {@link Fragment#onStart()}.
+ */
+ @UiThread
+ public void onStart() {
+ mUserLocationView.onStart();
+ }
+
+ /**
+ * You must call this method from the parent's {@link Activity#onStop()} or {@link Fragment#onStop()}
+ */
+ @UiThread
+ public void onStop() {
+ mUserLocationView.onStop();
+ }
+
+ /**
+ * You must call this method from the parent's {@link Activity#onPause()} or {@link Fragment#onPause()}.
+ */
+ @UiThread
+ public void onPause() {
+ // Register for connectivity changes
+ getContext().unregisterReceiver(mConnectivityReceiver);
+ mConnectivityReceiver = null;
+
+ mUserLocationView.pause();
+ mNativeMapView.pause();
+ }
+
+ /**
+ * You must call this method from the parent's {@link Activity#onResume()} or {@link Fragment#onResume()}.
+ */
+ @UiThread
+ public void onResume() {
+ // Register for connectivity changes
+ mConnectivityReceiver = new ConnectivityReceiver();
+ getContext().registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+
+ mUserLocationView.resume();
+ mNativeMapView.resume();
+ mNativeMapView.update();
+ }
+
+ /**
+ * You must call this method from the parent's {@link Activity#onLowMemory()} or {@link Fragment#onLowMemory()}.
+ */
+ @UiThread
+ public void onLowMemory() {
+ mNativeMapView.onLowMemory();
+ }
+
+ //
+ // Position
+ //
+
+ /**
+ * Returns the current coordinate at the center of the map view.
+ *
+ * @return The current coordinate.
+ */
+ @UiThread
+ @NonNull
+ public LatLng getCenterCoordinate() {
+ return mNativeMapView.getLatLng();
+ }
+
+ /**
+ * <p>
+ * Centers the map on a new coordinate immediately without changing the zoom level.
+ * </p>
+ * <p>
+ * The initial coordinate is (0, 0).
+ * </p>
+ * If you want to animate the change, use {@link MapView#setCenterCoordinate(LatLng, boolean)}.
+ *
+ * @param centerCoordinate The new coordinate.
+ * @see MapView#setCenterCoordinate(LatLng, boolean)
+ */
+ @UiThread
+ public void setCenterCoordinate(@NonNull LatLng centerCoordinate) {
+ setCenterCoordinate(centerCoordinate, false);
+ }
+
+ /**
+ * <p>
+ * Centers the map on a new coordinate without changing the zoom level and optionally animates the change.
+ * </p>
+ * The initial coordinate is (0, 0).
+ *
+ * @param centerCoordinate The new coordinate.
+ * @param animated If true, animates the change. If false, immediately changes the map.
+ */
+ @UiThread
+ public void setCenterCoordinate(@NonNull LatLng centerCoordinate, boolean animated) {
+ if (centerCoordinate == null) {
+ Log.w(TAG, "centerCoordinate was null, so just returning");
+ return;
+ }
+ long duration = animated ? ANIMATION_DURATION : 0;
+ mNativeMapView.cancelTransitions();
+ mNativeMapView.setLatLng(centerCoordinate, duration);
+ }
+
+
+ /**
+ * <p>
+ * Centers the map on a new coordinate immediately while changing the current zoom level.
+ * </p>
+ * <p>
+ * The initial value is a center coordinate of (0, 0) and a zoom level of 0.
+ * </p>
+ * If you want to animate the change, use {@link MapView#setCenterCoordinate(LatLngZoom, boolean)}.
+ *
+ * @param centerCoordinate The new coordinate and zoom level.
+ * @see MapView#setCenterCoordinate(LatLngZoom, boolean)
+ */
+ @UiThread
+ public void setCenterCoordinate(@NonNull LatLngZoom centerCoordinate) {
+ setCenterCoordinate(centerCoordinate, false);
+ }
+
+ /**
+ * Resets the map to the minimum zoom level, a center coordinate of (0, 0), a true north heading,
+ * and animates the change.
+ */
+ @UiThread
+ public void resetPosition() {
+ mNativeMapView.cancelTransitions();
+ mNativeMapView.resetPosition();
+ }
+
+ /**
+ * <p>
+ * Centers the map on a new coordinate while changing the zoom level and optionally animates the change.
+ * </p>
+ * The initial value is a center coordinate of (0, 0) and a zoom level of 0.
+ *
+ * @param centerCoordinate The new coordinate and zoom level.
+ * @param animated If true, animates the change. If false, immediately changes the map.
+ */
+ @UiThread
+ public void setCenterCoordinate(@NonNull LatLngZoom centerCoordinate,
+ boolean animated) {
+ if (centerCoordinate == null) {
+ Log.w(TAG, "centerCoordinate was null, so just returning");
+ return;
+ }
+ long duration = animated ? ANIMATION_DURATION : 0;
+ mNativeMapView.cancelTransitions();
+ mNativeMapView.setLatLngZoom(centerCoordinate, duration);
+ }
+
+ /**
+ * Returns whether the user may scroll around the map.
+ *
+ * @return If true, scrolling is enabled.
+ */
+ @UiThread
+ public boolean isScrollEnabled() {
+ return mScrollEnabled;
+ }
+
+ /**
+ * <p>
+ * Changes whether the user may scroll around the map.
+ * </p>
+ * <p>
+ * This setting controls only user interactions with the map. If you set the value to false,
+ * you may still change the map location programmatically.
+ * </p>
+ * The default value is true.
+ *
+ * @param scrollEnabled If true, scrolling is enabled.
+ */
+ @UiThread
+ public void setScrollEnabled(boolean scrollEnabled) {
+ this.mScrollEnabled = scrollEnabled;
+ }
+
+ //
+ // Pitch / Tilt
+ //
+
+ /**
+ * Gets the current Tilt in degrees of the MapView
+ * @return tilt in degrees
+ */
+ public double getTilt() {
+ return mNativeMapView.getPitch();
+ }
+
+ /**
+ * Sets the Tilt in degrees of the MapView.
+ * @param pitch New tilt in degrees
+ * @param duration Animation time in milliseconds. If null then 0 is used, making the animation immediate.
+ */
+ @FloatRange(from = MINIMUM_TILT, to = MAXIMUM_TILT)
+ public void setTilt(Double pitch, @Nullable Long duration) {
+ long actualDuration = 0;
+ if (duration != null) {
+ actualDuration = duration;
+ }
+ mNativeMapView.setPitch(pitch, actualDuration);
+ }
+
+ //
+ // Rotation
+ //
+
+ /**
+ * Returns the current heading of the map relative to true north.
+ *
+ * @return The current heading measured in degrees.
+ */
+ @UiThread
+ @FloatRange(from = 0, to = 360)
+ public double getDirection() {
+ double direction = -mNativeMapView.getBearing();
+
+ while (direction > 360) {
+ direction -= 360;
+ }
+ while (direction < 0) {
+ direction += 360;
+ }
+
+ return direction;
+ }
+
+ /**
+ * <p>
+ * Rotates the map to a new heading relative to true north immediately.
+ * </p>
+ * <ul>
+ * <li>The value 0 means that the top edge of the map view will correspond to true north.</li>
+ * <li>The value 90 means the top of the map will point due east.</li>
+ * <li>The value 180 means the top of the map will point due south.</li>
+ * <li>The value 270 means the top of the map will point due west.</li>
+ * </ul>
+ * <p>
+ * The initial heading is 0.
+ * </p>
+ * If you want to animate the change, use {@link MapView#setDirection(double, boolean)}.
+ *
+ * @param direction The new heading measured in degrees.
+ * @see MapView#setDirection(double, boolean)
+ */
+ @UiThread
+ public void setDirection(@FloatRange(from = 0, to = 360) double direction) {
+ setDirection(direction, false);
+ }
+
+ /**
+ * <p>
+ * Rotates the map to a new heading relative to true north and optionally animates the change.
+ * </p>
+ * <ul>
+ * <li>The value 0 means that the top edge of the map view will correspond to true north.</li>
+ * <li>The value 90 means the top of the map will point due east.</li>
+ * <li>The value 180 means the top of the map will point due south.</li>
+ * <li>The value 270 means the top of the map will point due west.</li>
+ * </ul>
+ * The initial heading is 0.
+ *
+ * @param direction The new heading measured in degrees from true north.
+ * @param animated If true, animates the change. If false, immediately changes the map.
+ */
+ @UiThread
+ public void setDirection(@FloatRange(from = 0, to = 360) double direction, boolean animated) {
+ long duration = animated ? ANIMATION_DURATION : 0;
+ mNativeMapView.cancelTransitions();
+ // Out of range direactions are normallised in setBearing
+ mNativeMapView.setBearing(-direction, duration);
+ }
+
+ /**
+ * Resets the map heading to true north and animates the change.
+ */
+ @UiThread
+ public void resetNorth() {
+ mNativeMapView.cancelTransitions();
+ mNativeMapView.resetNorth();
+ }
+
+ /**
+ * Returns whether the user may rotate the map.
+ *
+ * @return If true, rotating is enabled.
+ */
+ @UiThread
+ public boolean isRotateEnabled() {
+ return mRotateEnabled;
+ }
+
+ /**
+ * <p>
+ * Changes whether the user may rotate the map.
+ * </p>
+ * <p>
+ * This setting controls only user interactions with the map. If you set the value to false,
+ * you may still change the map location programmatically.
+ * </p>
+ * The default value is true.
+ *
+ * @param rotateEnabled If true, rotating is enabled.
+ */
+ @UiThread
+ public void setRotateEnabled(boolean rotateEnabled) {
+ this.mRotateEnabled = rotateEnabled;
+ }
+
+ //
+ // Scale
+ //
+
+ /**
+ * Returns the current zoom level of the map view.
+ *
+ * @return The current zoom level.
+ */
+ @UiThread
+ @FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL)
+ public double getZoomLevel() {
+ return mNativeMapView.getZoom();
+ }
+
+ /**
+ * <p>
+ * Zooms the map to a new zoom level immediately without changing the center coordinate.
+ * </p>
+ * <p>
+ * At zoom level 0, tiles cover the entire world map;
+ * at zoom level 1, tiles cover 1/14 of the world;
+ * at zoom level 2, tiles cover 1/16 of the world, and so on.
+ * </p>
+ * <p>
+ * The initial zoom level is 0. The maximum zoom level is {@link MapView#MAXIMUM_ZOOM_LEVEL}.
+ * </p>
+ * If you want to animate the change, use {@link MapView#setZoomLevel(double, boolean)}.
+ *
+ * @param zoomLevel The new coordinate.
+ * @see MapView#setZoomLevel(double, boolean)
+ * @see MapView#MAXIMUM_ZOOM_LEVEL
+ */
+ @UiThread
+ public void setZoomLevel(@FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL) double zoomLevel) {
+ setZoomLevel(zoomLevel, false);
+ }
+
+ /**
+ * <p>
+ * Zooms the map to a new zoom level and optionally animates the change without changing the center coordinate.
+ * </p>
+ * <p>
+ * At zoom level 0, tiles cover the entire world map;
+ * at zoom level 1, tiles cover 1/14 of the world;
+ * at zoom level 2, tiles cover 1/16 of the world, and so on.
+ * </p>
+ * The initial zoom level is 0. The maximum zoom level is {@link MapView#MAXIMUM_ZOOM_LEVEL}.
+ *
+ * @param zoomLevel The new coordinate.
+ * @param animated If true, animates the change. If false, immediately changes the map.
+ * @see MapView#MAXIMUM_ZOOM_LEVEL
+ */
+ @UiThread
+ public void setZoomLevel(@FloatRange(from = 0.0, to = MAXIMUM_ZOOM_LEVEL) double zoomLevel, boolean animated) {
+ if ((zoomLevel < 0.0) || (zoomLevel > MAXIMUM_ZOOM_LEVEL)) {
+ throw new IllegalArgumentException("zoomLevel is < 0 or > MapView.MAXIMUM_ZOOM_LEVEL");
+ }
+ long duration = animated ? ANIMATION_DURATION : 0;
+ mNativeMapView.cancelTransitions();
+ mNativeMapView.setZoom(zoomLevel, duration);
+ }
+
+ /**
+ * Returns whether the user may zoom the map.
+ *
+ * @return If true, zooming is enabled.
+ */
+ @UiThread
+ public boolean isZoomEnabled() {
+ return mZoomEnabled;
+ }
+
+ /**
+ * <p>
+ * Changes whether the user may zoom the map.
+ * </p>
+ * <p>
+ * This setting controls only user interactions with the map. If you set the value to false,
+ * you may still change the map location programmatically.
+ * </p>
+ * The default value is true.
+ *
+ * @param zoomEnabled If true, zooming is enabled.
+ */
+ @UiThread
+ public void setZoomEnabled(boolean zoomEnabled) {
+ this.mZoomEnabled = zoomEnabled;
+
+ if (mZoomControlsEnabled && (getVisibility() == View.VISIBLE) && mZoomEnabled) {
+ mZoomButtonsController.setVisible(true);
+ } else {
+ mZoomButtonsController.setVisible(false);
+ }
+ }
+
+ /**
+ * Gets whether the zoom controls are enabled.
+ *
+ * @return If true, the zoom controls are enabled.
+ */
+ public boolean isZoomControlsEnabled() {
+ return mZoomControlsEnabled;
+ }
+
+ /**
+ * <p>
+ * Sets whether the zoom controls are enabled.
+ * If enabled, the zoom controls are a pair of buttons
+ * (one for zooming in, one for zooming out) that appear on the screen.
+ * When pressed, they cause the camera to zoom in (or out) by one zoom level.
+ * If disabled, the zoom controls are not shown.
+ * </p>
+ * By default the zoom controls are enabled if the device is only single touch capable;
+ *
+ * @param enabled If true, the zoom controls are enabled.
+ */
+ public void setZoomControlsEnabled(boolean enabled) {
+ mZoomControlsEnabled = enabled;
+
+ if (mZoomControlsEnabled && (getVisibility() == View.VISIBLE) && mZoomEnabled) {
+ mZoomButtonsController.setVisible(true);
+ } else {
+ mZoomButtonsController.setVisible(false);
+ }
+ }
+
+ // Zoom in or out
+ private void zoom(boolean zoomIn) {
+ zoom(zoomIn, -1.0f, -1.0f);
+ }
+
+ private void zoom(boolean zoomIn, float x, float y) {
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ if (zoomIn) {
+ mNativeMapView.scaleBy(2.0, x / mScreenDensity, y / mScreenDensity, ANIMATION_DURATION);
+ } else {
+ mNativeMapView.scaleBy(0.5, x / mScreenDensity, y / mScreenDensity, ANIMATION_DURATION);
+ }
+ }
+
+ //
+ // Tilt
+ //
+
+ /**
+ * Returns whether the user may tilt the map.
+ *
+ * @return If true, tilting is enabled.
+ */
+ @UiThread
+ public boolean isTiltEnabled() {
+ return mTiltEnabled;
+ }
+
+ /**
+ * <p>
+ * Changes whether the user may tilt the map.
+ * </p>
+ * <p>
+ * This setting controls only user interactions with the map. If you set the value to false,
+ * you may still change the map location programmatically.
+ * </p>
+ * The default value is true.
+ *
+ * @param tiltEnabled If true, tilting is enabled.
+ */
+ @UiThread
+ public void setTiltEnabled(boolean tiltEnabled) {
+ this.mTiltEnabled = tiltEnabled;
+ }
+
+ //
+ // InfoWindows
+ //
+
+ /**
+ * Changes whether the map allows concurrent multiple infowindows to be shown.
+ *
+ * @param allow If true, map allows concurrent multiple infowindows to be shown.
+ */
+ @UiThread
+ public void setAllowConcurrentMultipleOpenInfoWindows(boolean allow) {
+ this.mAllowConcurrentMultipleOpenInfoWindows = allow;
+ }
+
+ /**
+ * Returns whether the map allows concurrent multiple infowindows to be shown.
+ *
+ * @return If true, map allows concurrent multiple infowindows to be shown.
+ */
+ @UiThread
+ public boolean isAllowConcurrentMultipleOpenInfoWindows() {
+ return this.mAllowConcurrentMultipleOpenInfoWindows;
+ }
+
+ //
+ // Debug
+ //
+
+ /**
+ * Returns whether the map debug information is currently shown.
+ *
+ * @return If true, map debug information is currently shown.
+ */
+ @UiThread
+ public boolean isDebugActive() {
+ return mNativeMapView.getDebug();
+ }
+
+ /**
+ * <p>
+ * Changes whether the map debug information is shown.
+ * </p>
+ * The default value is false.
+ *
+ * @param debugActive If true, map debug information is shown.
+ */
+ @UiThread
+ public void setDebugActive(boolean debugActive) {
+ mNativeMapView.setDebug(debugActive);
+ }
+
+ /**
+ * <p>
+ * Cycles through the map debug options.
+ * </p>
+ * The value of {@link MapView#isDebugActive()} reflects whether there are
+ * any map debug options enabled or disabled.
+ *
+ * @see MapView#isDebugActive()
+ */
+ @UiThread
+ public void cycleDebugOptions() {
+ mNativeMapView.cycleDebugOptions();
+ }
+
+ // True if map has finished loading the view
+ private boolean isFullyLoaded() {
+ return mNativeMapView.isFullyLoaded();
+ }
+
+ //
+ // Styling
+ //
+
+ /**
+ * <p>
+ * Loads a new map style from the specified URL.
+ * </p>
+ * {@code url} can take the following forms:
+ * <ul>
+ * <li>{@code Style.*}: load one of the bundled styles in {@link Style}.</li>
+ * <li>{@code mapbox://styles/<user>/<style>}:
+ * retrieves the style from a <a href="https://www.mapbox.com/account/">Mapbox account.</a>
+ * {@code user} is your username. {@code style} is the ID of your custom
+ * style created in <a href="https://www.mapbox.com/studio">Mapbox Studio</a>.</li>
+ * <li>{@code http://...} or {@code https://...}:
+ * retrieves the style over the Internet from any web server.</li>
+ * <li>{@code asset://...}:
+ * reads the style from the APK {@code assets/} directory.
+ * This is used to load a style bundled with your app.</li>
+ * <li>{@code null}: loads the default {@link Style#MAPBOX_STREETS} style.</li>
+ * </ul>
+ * <p>
+ * This method is asynchronous and will return immediately before the style finishes loading.
+ * If you wish to wait for the map to finish loading listen for the {@link MapView#DID_FINISH_LOADING_MAP} event.
+ * </p>
+ * If the style fails to load or an invalid style URL is set, the map view will become blank.
+ * An error message will be logged in the Android logcat and {@link MapView#DID_FAIL_LOADING_MAP} event will be sent.
+ *
+ * @param url The URL of the map style
+ * @see Style
+ */
+ @UiThread
+ public void setStyleUrl(@Nullable String url) {
+ if (url == null) {
+ url = Style.MAPBOX_STREETS;
+ }
+ mStyleUrl = url;
+ mNativeMapView.setStyleUrl(url);
+ }
+
+ /**
+ * <p>
+ * Loads a new map style from the specified bundled style.
+ * </p>
+ * <p>
+ * This method is asynchronous and will return immediately before the style finishes loading.
+ * If you wish to wait for the map to finish loading listen for the {@link MapView#DID_FINISH_LOADING_MAP} event.
+ * </p>
+ * If the style fails to load or an invalid style URL is set, the map view will become blank.
+ * An error message will be logged in the Android logcat and {@link MapView#DID_FAIL_LOADING_MAP} event will be sent.
+ *
+ * @param style The bundled style. Accepts one of the values from {@link Style}.
+ * @see Style
+ */
+ @UiThread
+ public void setStyle(@Style.StyleUrl String style) {
+ setStyleUrl(style);
+ }
+
+ /**
+ * <p>
+ * Returns the map style currently displayed in the map view.
+ * </p>
+ * If the default style is currently displayed, a URL will be returned instead of null.
+ *
+ * @return The URL of the map style.
+ */
+ @UiThread
+ @NonNull
+ public String getStyleUrl() {
+ return mStyleUrl;
+ }
+
+ /**
+ * Returns the set of currently active map style classes.
+ *
+ * @return A list of class identifiers.
+ */
+ @UiThread
+ @NonNull
+ public List<String> getStyleClasses() {
+ return Collections.unmodifiableList(mNativeMapView.getClasses());
+ }
+
+ /**
+ * <p>
+ * Changes the set of currently active map style classes immediately.
+ * </p>
+ * <p>
+ * The list of valid class identifiers is defined by the currently loaded map style.
+ * </p>
+ * If you want to animate the change, use {@link MapView#setStyleClasses(List, long)}.
+ *
+ * @param styleClasses A list of class identifiers.
+ * @see MapView#setStyleClasses(List, long)
+ * @see MapView#setStyleUrl(String)
+ */
+ @UiThread
+ public void setStyleClasses(@NonNull List<String> styleClasses) {
+ setStyleClasses(styleClasses, 0);
+ }
+
+ /**
+ * <p>
+ * Changes the set of currently active map style classes with an animated transition.
+ * </p>
+ * The list of valid class identifiers is defined by the currently loaded map style.
+ *
+ * @param styleClasses A list of class identifiers.
+ * @param transitionDuration The duration of the transition animation in milliseconds.
+ * @see MapView#setStyleClasses(List, long)
+ * @see MapView#setStyleUrl(String)
+ */
+ @UiThread
+ public void setStyleClasses(@NonNull List<String> styleClasses, @IntRange(from = 0) long transitionDuration) {
+ if (styleClasses == null) {
+ Log.w(TAG, "styleClasses was null, so just returning");
+ return;
+ }
+ if (transitionDuration < 0) {
+ throw new IllegalArgumentException("transitionDuration is < 0");
+ }
+ // TODO non negative check and annotation (go back and check other functions too)
+ mNativeMapView.setDefaultTransitionDuration(transitionDuration);
+ mNativeMapView.setClasses(styleClasses);
+ }
+
+ /**
+ * <p>
+ * Activates the specified map style class.
+ * </p>
+ * If you want to animate the change, use {@link MapView#setStyleClasses(List, long)}.
+ *
+ * @param styleClass The class identifier.
+ * @see MapView#setStyleClasses(List, long)
+ */
+ @UiThread
+ public void addStyleClass(@NonNull String styleClass) {
+ if (styleClass == null) {
+ Log.w(TAG, "styleClass was null, so just returning");
+ return;
+ }
+ mNativeMapView.addClass(styleClass);
+ }
+
+ /**
+ * <p>
+ * Deactivates the specified map style class.
+ * </p>
+ * If you want to animate the change, use {@link MapView#setStyleClasses(List, long)}.
+ *
+ * @param styleClass The class identifier.
+ * @see MapView#setStyleClasses(List, long)
+ */
+ @UiThread
+ public void removeStyleClass(@NonNull String styleClass) {
+ if (styleClass == null) {
+ Log.w(TAG, "styleClass was null, so just returning");
+ return;
+ }
+ mNativeMapView.removeClass(styleClass);
+ }
+
+ /**
+ * Returns whether the specified map style class is currently active.
+ *
+ * @param styleClass The class identifier.
+ * @return If true, the class is currently active.
+ */
+ @UiThread
+ public boolean hasStyleClass(@NonNull String styleClass) {
+ if (styleClass == null) {
+ Log.w(TAG, "centerCoordinate was null, so just returning false");
+ return false;
+ }
+ return mNativeMapView.hasClass(styleClass);
+ }
+
+ /**
+ * <p>
+ * Deactivates all the currently active map style classes immediately.
+ * </p>
+ * If you want to animate the change, use {@link MapView#removeAllStyleClasses(long)}.
+ *
+ * @see MapView#removeAllStyleClasses(long)
+ */
+ @UiThread
+ public void removeAllStyleClasses() {
+ removeAllStyleClasses(0);
+ }
+
+ /**
+ * Deactivates all the currently active map style classes with an animated transition.
+ *
+ * @param transitionDuration The duration of the transition animation in milliseconds.
+ */
+ @UiThread
+ public void removeAllStyleClasses(@IntRange(from = 0) long transitionDuration) {
+ if (transitionDuration < 0) {
+ throw new IllegalArgumentException("transitionDuration is < 0");
+ }
+ mNativeMapView.setDefaultTransitionDuration(transitionDuration);
+ ArrayList<String> styleClasses = new ArrayList<>(0);
+ setStyleClasses(styleClasses);
+ }
+
+ //
+ // Access token
+ //
+
+ // Checks if the given token is valid
+ private void validateAccessToken(String accessToken) {
+ if (TextUtils.isEmpty(accessToken) || (!accessToken.startsWith("pk.") && !accessToken.startsWith("sk."))) {
+ throw new InvalidAccessTokenException();
+ }
+ }
+
+ /**
+ * <p>
+ * Sets the current Mapbox access token used to load map styles and tiles.
+ * </p>
+ * <p>
+ * You must set a valid access token before you call {@link MapView#onCreate(Bundle)}
+ * or an exception will be thrown.
+ * </p>
+ * You can use {@link ApiAccess#getToken(Context)} to load an access token from your
+ * application's manifest.
+ *
+ * @param accessToken Your public Mapbox access token.
+ * @see MapView#onCreate(Bundle)
+ * @see ApiAccess#getToken(Context)
+ */
+ @UiThread
+ public void setAccessToken(@NonNull String accessToken) {
+ // validateAccessToken does the null check
+ if (!TextUtils.isEmpty(accessToken)) {
+ accessToken = accessToken.trim();
+ }
+ validateAccessToken(accessToken);
+ mNativeMapView.setAccessToken(accessToken);
+ }
+
+ /**
+ * Returns the current Mapbox access token used to load map styles and tiles.
+ *
+ * @return The current Mapbox access token.
+ */
+ @UiThread
+ @Nullable
+ public String getAccessToken() {
+ return mNativeMapView.getAccessToken();
+ }
+
+ //
+ // Projection
+ //
+
+ /**
+ * Converts a point in this view's coordinate system to a map coordinate.
+ *
+ * @param point A point in this view's coordinate system.
+ * @return The converted map coordinate.
+ */
+ @UiThread
+ @NonNull
+ public LatLng fromScreenLocation(@NonNull PointF point) {
+ if (point == null) {
+ Log.w(TAG, "point was null, so just returning (0, 0)");
+ return new LatLng();
+ }
+
+ float x = point.x;
+ float y = point.y;
+
+ // flip y direction vertically to match core GL
+ y = getHeight() - y;
+
+ return mNativeMapView.latLngForPixel(new PointF(x / mScreenDensity, y / mScreenDensity));
+ }
+
+ /**
+ * Converts a map coordinate to a point in this view's coordinate system.
+ *
+ * @param location A map coordinate.
+ * @return The converted point in this view's coordinate system.
+ */
+ @UiThread
+ @NonNull
+ public PointF toScreenLocation(@NonNull LatLng location) {
+ if (location == null) {
+ Log.w(TAG, "location was null, so just returning (0, 0)");
+ return new PointF();
+ }
+
+ PointF point = mNativeMapView.pixelForLatLng(location);
+
+ float x = point.x * mScreenDensity;
+ float y = point.y * mScreenDensity;
+
+ // flip y direction vertically to match core GL
+ y = getHeight() - y;
+
+ return new PointF(x, y);
+ }
+
+ //
+ // Annotations
+ //
+
+ public SpriteFactory getSpriteFactory() {
+ if (mSpriteFactory == null) {
+ mSpriteFactory = new SpriteFactory(this);
+ }
+ return mSpriteFactory;
+ }
+
+ private void loadSprite(Sprite sprite) {
+ Bitmap bitmap = sprite.getBitmap();
+ String id = sprite.getId();
+ if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) {
+ bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false);
+ }
+ ByteBuffer buffer = ByteBuffer.allocate(bitmap.getRowBytes() * bitmap.getHeight());
+ bitmap.copyPixelsToBuffer(buffer);
+
+ float density = bitmap.getDensity();
+ if (density == Bitmap.DENSITY_NONE) {
+ density = DisplayMetrics.DENSITY_DEFAULT;
+ }
+ float scale = density / DisplayMetrics.DENSITY_DEFAULT;
+
+ mNativeMapView.addAnnotationIcon(
+ id,
+ (int) (bitmap.getWidth() / scale),
+ (int) (bitmap.getHeight() / scale),
+ scale, buffer.array());
+ }
+
+ private void reloadSprites() {
+ int count = mSprites.size();
+ for (int i = 0; i < count; i++) {
+ Sprite sprite = mSprites.get(i);
+ loadSprite(sprite);
+ }
+ }
+
+ private Marker prepareMarker(MarkerOptions markerOptions) {
+ Marker marker = markerOptions.getMarker();
+ Sprite icon = marker.getIcon();
+ if (icon == null) {
+ icon = getSpriteFactory().defaultMarker();
+ marker.setIcon(icon);
+ }
+ if (!mSprites.contains(icon)) {
+ mSprites.add(icon);
+ loadSprite(icon);
+ } else {
+ Sprite oldSprite = mSprites.get(mSprites.indexOf(icon));
+ if (!oldSprite.getBitmap().sameAs(icon.getBitmap())) {
+ throw new SpriteBitmapChangedException();
+ }
+ }
+ marker.setTopOffsetPixels(getTopOffsetPixelsForSprite(icon));
+ return marker;
+ }
+
+ /**
+ * <p>
+ * Adds a marker to this map.
+ * </p>
+ * The marker's icon is rendered on the map at the location {@code Marker.position}.
+ * If {@code Marker.title} is defined, the map shows an info box with the marker's title and snippet.
+ *
+ * @param markerOptions A marker options object that defines how to render the marker.
+ * @return The {@code Marker} that was added to the map.
+ */
+ @UiThread
+ @NonNull
+ public Marker addMarker(@NonNull MarkerOptions markerOptions) {
+ if (markerOptions == null) {
+ Log.w(TAG, "markerOptions was null, so just returning null");
+ return null;
+ }
+
+ Marker marker = prepareMarker(markerOptions);
+ long id = mNativeMapView.addMarker(marker);
+ marker.setId(id); // the annotation needs to know its id
+ marker.setMapView(this); // the annotation needs to know which map view it is in
+ mAnnotations.add(marker);
+ return marker;
+ }
+
+ /**
+ * <p>
+ * Adds multiple markers to this map.
+ * </p>
+ * The marker's icon is rendered on the map at the location {@code Marker.position}.
+ * If {@code Marker.title} is defined, the map shows an info box with the marker's title and snippet.
+ *
+ * @param markerOptionsList A list of marker options objects that defines how to render the markers.
+ * @return A list of the {@code Marker}s that were added to the map.
+ */
+ @UiThread
+ @NonNull
+ public List<Marker> addMarkers(@NonNull List<MarkerOptions> markerOptionsList) {
+ if (markerOptionsList == null) {
+ Log.w(TAG, "markerOptionsList was null, so just returning null");
+ return null;
+ }
+
+ int count = markerOptionsList.size();
+ List<Marker> markers = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ MarkerOptions markerOptions = markerOptionsList.get(i);
+ Marker marker = prepareMarker(markerOptions);
+ markers.add(marker);
+ }
+
+ long[] ids = mNativeMapView.addMarkers(markers);
+
+ Marker m;
+ for (int i = 0; i < count; i++) {
+ m = markers.get(i);
+ m.setId(ids[i]);
+ m.setMapView(this);
+ mAnnotations.add(m);
+ }
+
+ return new ArrayList<>(markers);
+ }
+
+ /**
+ * Adds a polyline to this map.
+ *
+ * @param polylineOptions A polyline options object that defines how to render the polyline.
+ * @return The {@code Polyine} that was added to the map.
+ */
+ @UiThread
+ @NonNull
+ public Polyline addPolyline(@NonNull PolylineOptions polylineOptions) {
+ if (polylineOptions == null) {
+ Log.w(TAG, "polylineOptions was null, so just returning null");
+ return null;
+ }
+
+ Polyline polyline = polylineOptions.getPolyline();
+ long id = mNativeMapView.addPolyline(polyline);
+ polyline.setId(id);
+ polyline.setMapView(this);
+ mAnnotations.add(polyline);
+ return polyline;
+ }
+
+ /**
+ * Adds multiple polylines to this map.
+ *
+ * @param polylineOptionsList A list of polyline options objects that defines how to render the polylines.
+ * @return A list of the {@code Polyline}s that were added to the map.
+ */
+ @UiThread
+ @NonNull
+ public List<Polyline> addPolylines(@NonNull List<PolylineOptions> polylineOptionsList) {
+ if (polylineOptionsList == null) {
+ Log.w(TAG, "polylineOptionsList was null, so just returning null");
+ return null;
+ }
+
+ int count = polylineOptionsList.size();
+ List<Polyline> polylines = new ArrayList<>(count);
+ for (PolylineOptions options : polylineOptionsList) {
+ polylines.add(options.getPolyline());
+ }
+
+ long[] ids = mNativeMapView.addPolylines(polylines);
+
+ Polyline p;
+ for (int i = 0; i < count; i++) {
+ p = polylines.get(i);
+ p.setId(ids[i]);
+ p.setMapView(this);
+ mAnnotations.add(p);
+ }
+
+ return new ArrayList<>(polylines);
+ }
+
+ /**
+ * Adds a polygon to this map.
+ *
+ * @param polygonOptions A polygon options object that defines how to render the polygon.
+ * @return The {@code Polygon} that was added to the map.
+ */
+ @UiThread
+ @NonNull
+ public Polygon addPolygon(@NonNull PolygonOptions polygonOptions) {
+ if (polygonOptions == null) {
+ Log.w(TAG, "polygonOptions was null, so just returning null");
+ return null;
+ }
+
+ Polygon polygon = polygonOptions.getPolygon();
+ long id = mNativeMapView.addPolygon(polygon);
+ polygon.setId(id);
+ polygon.setMapView(this);
+ mAnnotations.add(polygon);
+ return polygon;
+ }
+
+
+ /**
+ * Adds multiple polygons to this map.
+ *
+ * @param polygonOptionsList A list of polygon options objects that defines how to render the polygons.
+ * @return A list of the {@code Polygon}s that were added to the map.
+ */
+ @UiThread
+ @NonNull
+ public List<Polygon> addPolygons(@NonNull List<PolygonOptions> polygonOptionsList) {
+ if (polygonOptionsList == null) {
+ Log.w(TAG, "polygonOptionsList was null, so just returning null");
+ return null;
+ }
+
+ int count = polygonOptionsList.size();
+ List<Polygon> polygons = new ArrayList<>(count);
+ for (PolygonOptions polygonOptions : polygonOptionsList) {
+ polygons.add(polygonOptions.getPolygon());
+ }
+
+ long[] ids = mNativeMapView.addPolygons(polygons);
+
+ Polygon p;
+ for (int i = 0; i < count; i++) {
+ p = polygons.get(i);
+ p.setId(ids[i]);
+ p.setMapView(this);
+ mAnnotations.add(p);
+ }
+
+ return new ArrayList<>(polygons);
+ }
+
+
+ /**
+ * <p>
+ * Convenience method for removing a Marker from the map.
+ * </p>
+ * Calls removeAnnotation() internally
+ *
+ * @param marker Marker to remove
+ */
+ @UiThread
+ public void removeMarker(@NonNull Marker marker) {
+ removeAnnotation(marker);
+ }
+
+ /**
+ * Removes an annotation from the map.
+ *
+ * @param annotation The annotation object to remove.
+ */
+ @UiThread
+ public void removeAnnotation(@NonNull Annotation annotation) {
+ if (annotation == null) {
+ Log.w(TAG, "annotation was null, so just returning");
+ return;
+ }
+
+ if (annotation instanceof Marker) {
+ ((Marker) annotation).hideInfoWindow();
+ }
+ long id = annotation.getId();
+ mNativeMapView.removeAnnotation(id);
+ mAnnotations.remove(annotation);
+ }
+
+ /**
+ * Removes multiple annotations from the map.
+ *
+ * @param annotationList A list of annotation objects to remove.
+ */
+ @UiThread
+ public void removeAnnotations(@NonNull List<? extends Annotation> annotationList) {
+ if (annotationList == null) {
+ Log.w(TAG, "annotationList was null, so just returning");
+ return;
+ }
+
+ int count = annotationList.size();
+ long[] ids = new long[count];
+ for (int i = 0; i < count; i++) {
+ ids[i] = annotationList.get(i).getId();
+ }
+ mNativeMapView.removeAnnotations(ids);
+ }
+
+ /**
+ * Removes all annotations from the map.
+ */
+ @UiThread
+ public void removeAllAnnotations() {
+ int count = mAnnotations.size();
+ long[] ids = new long[mAnnotations.size()];
+
+ for (int i = 0; i < count; i++) {
+ Annotation annotation = mAnnotations.get(i);
+ long id = annotation.getId();
+ ids[i] = id;
+ if (annotation instanceof Marker) {
+ ((Marker) annotation).hideInfoWindow();
+ }
+ }
+
+ mNativeMapView.removeAnnotations(ids);
+ mAnnotations.clear();
+ }
+
+ /**
+ * Returns a list of all the annotations on the map.
+ *
+ * @return A list of all the annotation objects. The returned object is a copy so modifying this
+ * list will not update the map.
+ */
+ @NonNull
+ public List<Annotation> getAllAnnotations() {
+ return new ArrayList<>(mAnnotations);
+ }
+
+ private List<Marker> getMarkersInBounds(@NonNull BoundingBox bbox) {
+ if (bbox == null) {
+ Log.w(TAG, "bbox was null, so just returning null");
+ return null;
+ }
+
+ // TODO: filter in JNI using C++ parameter to getAnnotationsInBounds
+ long[] ids = mNativeMapView.getAnnotationsInBounds(bbox);
+
+ List<Long> idsList = new ArrayList<>(ids.length);
+ for (int i = 0; i < ids.length; i++) {
+ idsList.add(ids[i]);
+ }
+
+ List<Marker> annotations = new ArrayList<>(ids.length);
+ int count = mAnnotations.size();
+ for (int i = 0; i < count; i++) {
+ Annotation annotation = mAnnotations.get(i);
+ if (annotation instanceof Marker && idsList.contains(annotation.getId())) {
+ annotations.add((Marker) annotation);
+ }
+ }
+
+ return new ArrayList<>(annotations);
+ }
+
+ private int getTopOffsetPixelsForSprite(Sprite sprite) {
+ // This method will dead lock if map paused. Causes a freeze if you add a marker in an
+ // activity's onCreate()
+ if (mNativeMapView.isPaused()) {
+ return 0;
+ }
+
+ return (int) (mNativeMapView.getTopOffsetPixelsForAnnotationSymbol(sprite.getId())
+ * mScreenDensity);
+ }
+
+ /**
+ * <p>
+ * Returns the distance spanned by one pixel at the specified latitude and current zoom level.
+ * </p>
+ * The distance between pixels decreases as the latitude approaches the poles.
+ * This relationship parallels the relationship between longitudinal coordinates at different latitudes.
+ *
+ * @param latitude The latitude for which to return the value.
+ * @return The distance measured in meters.
+ */
+ @UiThread
+ public double getMetersPerPixelAtLatitude(@FloatRange(from = -180, to = 180) double latitude) {
+ return mNativeMapView.getMetersPerPixelAtLatitude(latitude, getZoomLevel()) / mScreenDensity;
+ }
+
+ /**
+ * <p>
+ * Selects a marker. The selected marker will have it's info window opened.
+ * Any other open info windows will be closed unless isAllowConcurrentMultipleOpenInfoWindows()
+ * is true.
+ * </p>
+ * Selecting an already selected marker will have no effect.
+ *
+ * @param marker The marker to select.
+ */
+ @UiThread
+ public void selectMarker(@NonNull Marker marker) {
+ if (marker == null) {
+ Log.w(TAG, "marker was null, so just returning");
+ return;
+ }
+
+ if (mSelectedMarkers.contains(marker)) {
+ return;
+ }
+
+ // Need to deselect any currently selected annotation first
+ if (!isAllowConcurrentMultipleOpenInfoWindows()) {
+ deselectMarkers();
+ }
+
+ boolean handledDefaultClick = false;
+ if (mOnMarkerClickListener != null) {
+ // end developer has provided a custom click listener
+ handledDefaultClick = mOnMarkerClickListener.onMarkerClick(marker);
+ }
+
+ if (!handledDefaultClick) {
+ // default behaviour show InfoWindow
+ mInfoWindows.add(marker.showInfoWindow());
+ }
+
+ mSelectedMarkers.add(marker);
+ }
+
+ /**
+ * Deselects any currently selected marker. All markers will have it's info window closed.
+ */
+ @UiThread
+ public void deselectMarkers() {
+ if (mSelectedMarkers.isEmpty()) {
+ return;
+ }
+
+ for (Marker marker : mSelectedMarkers) {
+ if (marker.isInfoWindowShown()) {
+ marker.hideInfoWindow();
+ }
+ }
+
+ // Removes all selected markers from the list
+ mSelectedMarkers.clear();
+ }
+
+ /**
+ * Deselects a currently selected marker. The selected marker will have it's info window closed.
+ */
+ @UiThread
+ public void deselectMarker(@NonNull Marker marker) {
+ if (!mSelectedMarkers.contains(marker)) {
+ return;
+ }
+
+ if (marker.isInfoWindowShown()) {
+ marker.hideInfoWindow();
+ }
+
+ mSelectedMarkers.remove(marker);
+ }
+
+ //
+ // Camera
+ //
+
+ /**
+ * Changes the map's viewport to fit the given coordinate bounds.
+ *
+ * @param bounds The bounds that the viewport will show in its entirety.
+ */
+ @UiThread
+ public void setVisibleCoordinateBounds(@NonNull CoordinateBounds bounds) {
+ setVisibleCoordinateBounds(bounds, false);
+ }
+
+ /**
+ * Changes the map's viewing area to fit the given coordinate bounds, optionally animating the change.
+ *
+ * @param bounds The bounds that the viewport will show in its entirety.
+ * @param animated If true, animates the change. If false, immediately changes the map.
+ */
+ @UiThread
+ public void setVisibleCoordinateBounds(@NonNull CoordinateBounds bounds, boolean animated) {
+ setVisibleCoordinateBounds(bounds, new RectF(), animated);
+ }
+
+ /**
+ * Changes the map’s viewport to fit the given coordinate bounds with additional padding at the
+ * edge of the map, optionally animating the change.
+ *
+ * @param bounds The bounds that the viewport will show in its entirety.
+ * @param padding The minimum padding (in pixels) that will be visible around the given coordinate bounds.
+ * @param animated If true, animates the change. If false, immediately changes the map.
+ */
+ @UiThread
+ public void setVisibleCoordinateBounds(@NonNull CoordinateBounds bounds, @NonNull RectF padding, boolean animated) {
+ LatLng[] coordinates = {
+ new LatLng(bounds.getNorthEast().getLatitude(), bounds.getSouthWest().getLongitude()),
+ bounds.getSouthWest(),
+ new LatLng(bounds.getSouthWest().getLatitude(), bounds.getNorthEast().getLongitude()),
+ bounds.getNorthEast()
+
+ };
+ setVisibleCoordinateBounds(coordinates, padding, animated);
+ }
+
+ /**
+ * Changes the map’s viewport to fit the given coordinates, optionally some additional padding on each side
+ * and animating the change.
+ *
+ * @param coordinates The coordinates that the viewport will show.
+ * @param padding The minimum padding (in pixels) that will be visible around the given coordinate bounds.
+ * @param animated If true, animates the change. If false, immediately changes the map.
+ */
+ @UiThread
+ public void setVisibleCoordinateBounds(@NonNull LatLng[] coordinates, @NonNull RectF padding, boolean animated) {
+ setVisibleCoordinateBounds(coordinates, padding, getDirection(), animated);
+ }
+
+ private void setVisibleCoordinateBounds(LatLng[] coordinates, RectF padding, double direction, boolean animated) {
+ setVisibleCoordinateBounds(coordinates, padding, direction, animated ? ANIMATION_DURATION : 0l);
+ }
+
+ private void setVisibleCoordinateBounds(LatLng[] coordinates, RectF padding, double direction, long duration) {
+ mNativeMapView.setVisibleCoordinateBounds(coordinates, new RectF(padding.left / mScreenDensity,
+ padding.top / mScreenDensity, padding.right / mScreenDensity, padding.bottom / mScreenDensity),
+ direction, duration);
+ }
+
+ /**
+ * Gets the currently selected marker.
+ *
+ * @return The currently selected marker.
+ */
+ @UiThread
+ @Nullable
+ public List<Marker> getSelectedMarkers() {
+ return mSelectedMarkers;
+ }
+
+ private void adjustTopOffsetPixels() {
+ int count = mAnnotations.size();
+ for (int i = 0; i < count; i++) {
+ Annotation annotation = mAnnotations.get(i);
+ if (annotation instanceof Marker) {
+ Marker marker = (Marker) annotation;
+ marker.setTopOffsetPixels(
+ getTopOffsetPixelsForSprite(marker.getIcon()));
+ }
+ }
+
+ for (Marker marker : mSelectedMarkers) {
+ if (marker.isInfoWindowShown()) {
+ Marker temp = marker;
+ temp.hideInfoWindow();
+ temp.showInfoWindow();
+ marker = temp;
+ }
+ }
+ }
+
+ private void reloadMarkers() {
+ int count = mAnnotations.size();
+ for (int i = 0; i < count; i++) {
+ Annotation annotation = mAnnotations.get(i);
+ if (annotation instanceof Marker) {
+ Marker marker = (Marker) annotation;
+ mNativeMapView.removeAnnotation(annotation.getId());
+ long newId = mNativeMapView.addMarker(marker);
+ marker.setId(newId);
+ }
+ }
+ }
+
+ //
+ // Rendering
+ //
+
+ // Called when the map needs to be rerendered
+ // Called via JNI from NativeMapView
+ protected void onInvalidate() {
+ postInvalidate();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (isInEditMode()) {
+ return;
+ }
+
+ if (!mNativeMapView.isPaused()) {
+ mNativeMapView.renderSync();
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int width, int height, int oldw, int oldh) {
+ if (!isInEditMode()) {
+ mNativeMapView.resizeView((int) (width / mScreenDensity), (int) (height / mScreenDensity));
+ }
+ }
+
+ // This class handles TextureView callbacks
+ private class SurfaceTextureListener implements TextureView.SurfaceTextureListener {
+
+ // Called when the native surface texture has been created
+ // Must do all EGL/GL ES initialization here
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+ mNativeMapView.createSurface(new Surface(surface));
+ mNativeMapView.resizeFramebuffer(width, height);
+ }
+
+ // Called when the native surface texture has been destroyed
+ // Must do all EGL/GL ES destruction here
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ if (mNativeMapView != null) {
+ mNativeMapView.destroySurface();
+ }
+ return true;
+ }
+
+ // Called when the format or size of the native surface texture has been changed
+ // Must handle window resizing here.
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ mNativeMapView.resizeFramebuffer(width, height);
+ }
+
+ // Called when the SurfaceTexure frame is drawn to screen
+ // Must sync with UI here
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ mCompassView.update(getDirection());
+ mUserLocationView.update();
+ for (InfoWindow infoWindow : mInfoWindows) {
+ infoWindow.update();
+ }
+ }
+ }
+
+ // Used by UserLocationView
+ void update() {
+ if (mNativeMapView != null) {
+ mNativeMapView.update();
+ }
+ }
+
+ // Used by UserLocationView
+ void setBearing(float bearing) {
+ mNativeMapView.setBearing(bearing, 100);
+ }
+
+ //
+ // View events
+ //
+
+ // Called when view is no longer connected
+ @Override
+ @CallSuper
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ // Required by ZoomButtonController (from Android SDK documentation)
+ if (mZoomControlsEnabled) {
+ mZoomButtonsController.setVisible(false);
+ }
+ }
+
+ // Called when view is hidden and shown
+ @Override
+ protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
+ // Required by ZoomButtonController (from Android SDK documentation)
+ if (mZoomControlsEnabled && (visibility != View.VISIBLE)) {
+ mZoomButtonsController.setVisible(false);
+ }
+ if (mZoomControlsEnabled && (visibility == View.VISIBLE)
+ && mZoomEnabled) {
+ mZoomButtonsController.setVisible(true);
+ }
+ }
+
+ //
+ // Touch events
+ //
+
+ /**
+ * <p>
+ * Sets the preference for whether all gestures should be enabled or disabled.
+ * </p>
+ * <p>
+ * This setting controls only user interactions with the map. If you set the value to false,
+ * you may still change the map location programmatically.
+ * </p>
+ * The default value is true.
+ *
+ * @param enabled If true, all gestures are available; otherwise, all gestures are disabled.
+ * @see MapView#setZoomEnabled(boolean)
+ * @see MapView#setScrollEnabled(boolean)
+ * @see MapView#setRotateEnabled(boolean)
+ * @see MapView#setTiltEnabled(boolean)
+ */
+ public void setAllGesturesEnabled(boolean enabled) {
+ setZoomEnabled(enabled);
+ setScrollEnabled(enabled);
+ setRotateEnabled(enabled);
+ setTiltEnabled(enabled);
+ }
+
+ // Called when user touches the screen, all positions are absolute
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ // Check and ignore non touch or left clicks
+
+ if ((event.getButtonState() != 0) && (event.getButtonState() != MotionEvent.BUTTON_PRIMARY)) {
+ return false;
+ }
+
+ // Check two finger gestures first
+ mRotateGestureDetector.onTouchEvent(event);
+ mScaleGestureDetector.onTouchEvent(event);
+ mShoveGestureDetector.onTouchEvent(event);
+
+ // Handle two finger tap
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ // First pointer down
+ mNativeMapView.setGestureInProgress(true);
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // Second pointer down
+ mTwoTap = event.getPointerCount() == 2;
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ // Second pointer up
+ break;
+
+ case MotionEvent.ACTION_UP:
+ // First pointer up
+ long tapInterval = event.getEventTime() - event.getDownTime();
+ boolean isTap = tapInterval <= ViewConfiguration.getTapTimeout();
+ boolean inProgress = mRotateGestureDetector.isInProgress()
+ || mScaleGestureDetector.isInProgress()
+ || mShoveGestureDetector.isInProgress();
+
+ if (mTwoTap && isTap && !inProgress) {
+ PointF focalPoint = TwoFingerGestureDetector.determineFocalPoint(event);
+ zoom(false, focalPoint.x, focalPoint.y);
+ mTwoTap = false;
+ return true;
+ }
+
+ mTwoTap = false;
+ mNativeMapView.setGestureInProgress(false);
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ mTwoTap = false;
+ mNativeMapView.setGestureInProgress(false);
+ break;
+ }
+
+ boolean retVal = mGestureDetector.onTouchEvent(event);
+ return retVal || super.onTouchEvent(event);
+ }
+
+ // This class handles one finger gestures
+ private class GestureListener extends
+ GestureDetector.SimpleOnGestureListener {
+
+ // Must always return true otherwise all events are ignored
+ @Override
+ public boolean onDown(MotionEvent e) {
+ // Show the zoom controls
+ if (mZoomControlsEnabled && mZoomEnabled) {
+ mZoomButtonsController.setVisible(true);
+ }
+
+ return true;
+ }
+
+ // Called for double taps
+ @Override
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ switch (e.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ break;
+ case MotionEvent.ACTION_MOVE:
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mQuickZoom) {
+ mQuickZoom = false;
+ break;
+ }
+
+ // Single finger double tap
+ if (mUserLocationView.getMyLocationTrackingMode() == MyLocationTracking.TRACKING_NONE) {
+ // Zoom in on gesture
+ zoom(true, e.getX(), e.getY());
+ } else {
+ // Zoom in on center map
+ zoom(true, getWidth() / 2, getHeight() / 2);
+ }
+ break;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+ return true;
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ // Open / Close InfoWindow
+ PointF tapPoint = new PointF(e.getX(), e.getY());
+
+ final float toleranceSides = 30 * mScreenDensity;
+ final float toleranceTop = 40 * mScreenDensity;
+ final float toleranceBottom = 10 * mScreenDensity;
+
+ RectF tapRect = new RectF(tapPoint.x - toleranceSides, tapPoint.y + toleranceTop,
+ tapPoint.x + toleranceSides, tapPoint.y - toleranceBottom);
+
+ List<LatLng> corners = Arrays.asList(
+ fromScreenLocation(new PointF(tapRect.left, tapRect.bottom)),
+ fromScreenLocation(new PointF(tapRect.left, tapRect.top)),
+ fromScreenLocation(new PointF(tapRect.right, tapRect.top)),
+ fromScreenLocation(new PointF(tapRect.right, tapRect.bottom))
+ );
+
+ BoundingBox tapBounds = BoundingBox.fromLatLngs(corners);
+
+ List<Marker> nearbyMarkers = getMarkersInBounds(tapBounds);
+
+ long newSelectedMarkerId;
+
+ if (nearbyMarkers.size() > 0) {
+
+ // there is at least one nearby marker; select one
+ //
+ // first, sort for comparison and iteration
+ Collections.sort(nearbyMarkers);
+
+ if (nearbyMarkers == mMarkersNearLastTap) {
+
+ // TODO: We still need to adapt this logic to the new mSelectedMarkers list,
+ // though the basic functionality is there.
+
+ // the selection candidates haven't changed; cycle through them
+// if (mSelectedMarker != null
+// && (mSelectedMarker.getId() == mMarkersNearLastTap.get(mMarkersNearLastTap.size() - 1).getId())) {
+// // the selected marker is the last in the set; cycle back to the first
+// // note: this could be the selected marker if only one in set
+// newSelectedMarkerId = mMarkersNearLastTap.get(0).getId();
+// } else if (mSelectedMarker != null) {
+// // otherwise increment the selection through the candidates
+// long result = mMarkersNearLastTap.indexOf(mSelectedMarker);
+// newSelectedMarkerId = mMarkersNearLastTap.get((int) result + 1).getId();
+// } else {
+ // no current selection; select the first one
+ newSelectedMarkerId = mMarkersNearLastTap.get(0).getId();
+// }
+ } else {
+ // start tracking a new set of nearby markers
+ mMarkersNearLastTap = nearbyMarkers;
+
+ // select the first one
+ newSelectedMarkerId = mMarkersNearLastTap.get(0).getId();
+ }
+
+ } else {
+ // there are no nearby markers; deselect if necessary
+ newSelectedMarkerId = -1;
+ }
+
+ if (newSelectedMarkerId >= 0) {
+
+ int count = mAnnotations.size();
+ for (int i = 0; i < count; i++) {
+ Annotation annotation = mAnnotations.get(i);
+ if (annotation instanceof Marker) {
+ if (annotation.getId() == newSelectedMarkerId) {
+ if (mSelectedMarkers.isEmpty() || !mSelectedMarkers.contains(annotation)) {
+ selectMarker((Marker) annotation);
+ }
+ break;
+ }
+ }
+ }
+
+ } else {
+ // deselect any selected marker
+ deselectMarkers();
+
+ // notify app of map click
+ if (mOnMapClickListener != null) {
+ LatLng point = fromScreenLocation(tapPoint);
+ mOnMapClickListener.onMapClick(point);
+ }
+ }
+
+ return true;
+ }
+
+ // Called for a long press
+ @Override
+ public void onLongPress(MotionEvent e) {
+ if (mOnMapLongClickListener != null && !mQuickZoom) {
+ LatLng point = fromScreenLocation(new PointF(e.getX(), e.getY()));
+ mOnMapLongClickListener.onMapLongClick(point);
+ }
+ }
+
+ // Called for flings
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Fling the map
+ float ease = 0.25f;
+
+ velocityX = velocityX * ease;
+ velocityY = velocityY * ease;
+
+ double speed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
+ double deceleration = 2500;
+ double duration = speed / (deceleration * ease);
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ mNativeMapView.moveBy(velocityX * duration / 2.0 / mScreenDensity, velocityY * duration / 2.0 / mScreenDensity, (long) (duration * 1000.0f));
+
+ if (mOnFlingListener != null) {
+ mOnFlingListener.onFling();
+ }
+
+ return true;
+ }
+
+ // Called for drags
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Scroll the map
+ mNativeMapView.moveBy(-distanceX / mScreenDensity, -distanceY / mScreenDensity);
+
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScroll();
+ }
+
+ return true;
+ }
+ }
+
+ // This class handles two finger gestures and double-tap drag gestures
+ private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+
+ long mBeginTime = 0;
+ float mScaleFactor = 1.0f;
+
+ // Called when two fingers first touch the screen
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ mBeginTime = detector.getEventTime();
+ return true;
+ }
+
+ // Called when fingers leave screen
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ mBeginTime = 0;
+ mScaleFactor = 1.0f;
+ mZoomStarted = false;
+ }
+
+ // Called each time a finger moves
+ // Called for pinch zooms and quickzooms/quickscales
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ // If scale is large enough ignore a tap
+ mScaleFactor *= detector.getScaleFactor();
+ if ((mScaleFactor > 1.05f) || (mScaleFactor < 0.95f)) {
+ mZoomStarted = true;
+ }
+
+ // Ignore short touches in case it is a tap
+ // Also ignore small scales
+ long time = detector.getEventTime();
+ long interval = time - mBeginTime;
+ if (!mZoomStarted && (interval <= ViewConfiguration.getTapTimeout())) {
+ return false;
+ }
+
+ if (!mZoomStarted) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Gesture is a quickzoom if there aren't two fingers
+ mQuickZoom = !mTwoTap;
+
+ // Scale the map
+ if (!mQuickZoom && mUserLocationView.getMyLocationTrackingMode() == MyLocationTracking.TRACKING_NONE) {
+ // around gesture
+ mNativeMapView.scaleBy(detector.getScaleFactor(), detector.getFocusX() / mScreenDensity, detector.getFocusY() / mScreenDensity);
+ } else {
+ // around center map
+ mNativeMapView.scaleBy(detector.getScaleFactor(), (getWidth() / 2) / mScreenDensity, (getHeight() / 2) / mScreenDensity);
+ }
+ return true;
+ }
+ }
+
+ // This class handles two finger rotate gestures
+ private class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
+
+ long mBeginTime = 0;
+ float mTotalAngle = 0.0f;
+ boolean mStarted = false;
+
+ // Called when two fingers first touch the screen
+ @Override
+ public boolean onRotateBegin(RotateGestureDetector detector) {
+ if (!mRotateEnabled) {
+ return false;
+ }
+
+ mBeginTime = detector.getEventTime();
+ return true;
+ }
+
+ // Called when the fingers leave the screen
+ @Override
+ public void onRotateEnd(RotateGestureDetector detector) {
+ mBeginTime = 0;
+ mTotalAngle = 0.0f;
+ mStarted = false;
+ }
+
+ // Called each time one of the two fingers moves
+ // Called for rotation
+ @Override
+ public boolean onRotate(RotateGestureDetector detector) {
+ if (!mRotateEnabled) {
+ return false;
+ }
+
+ // If rotate is large enough ignore a tap
+ // Also is zoom already started, don't rotate
+ mTotalAngle += detector.getRotationDegreesDelta();
+ if (!mZoomStarted && ((mTotalAngle > 10.0f) || (mTotalAngle < -10.0f))) {
+ mStarted = true;
+ }
+
+ // Ignore short touches in case it is a tap
+ // Also ignore small rotate
+ long time = detector.getEventTime();
+ long interval = time - mBeginTime;
+ if (!mStarted && (interval <= ViewConfiguration.getTapTimeout())) {
+ return false;
+ }
+
+ if (!mStarted) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Get rotate value
+ double bearing = mNativeMapView.getBearing();
+ bearing += detector.getRotationDegreesDelta();
+
+ // Rotate the map
+ if (mUserLocationView.getMyLocationTrackingMode() == MyLocationTracking.TRACKING_NONE) {
+ // around gesture
+ mNativeMapView.setBearing(bearing,
+ detector.getFocusX() / mScreenDensity,
+ (getHeight() - detector.getFocusY()) / mScreenDensity);
+ } else {
+ // around center map
+ mNativeMapView.setBearing(bearing,
+ (getWidth() / 2) / mScreenDensity,
+ (getHeight() / 2) / mScreenDensity);
+ }
+ return true;
+ }
+ }
+
+ // This class handles a vertical two-finger shove. (If you place two fingers on screen with
+ // less than a 20 degree angle between them, this will detect movement on the Y-axis.)
+ private class ShoveGestureListener implements ShoveGestureDetector.OnShoveGestureListener {
+
+ long mBeginTime = 0;
+ float mTotalDelta = 0.0f;
+ boolean mStarted = false;
+
+ @Override
+ public boolean onShoveBegin(ShoveGestureDetector detector) {
+ if (!mTiltEnabled) {
+ return false;
+ }
+
+ mBeginTime = detector.getEventTime();
+ return true;
+ }
+
+ @Override
+ public void onShoveEnd(ShoveGestureDetector detector) {
+ mBeginTime = 0;
+ mTotalDelta = 0.0f;
+ mStarted = false;
+ }
+
+ @Override
+ public boolean onShove(ShoveGestureDetector detector) {
+ if (!mTiltEnabled) {
+ return false;
+ }
+
+ // If tilt is large enough ignore a tap
+ // Also if zoom already started, don't tilt
+ mTotalDelta += detector.getShovePixelsDelta();
+ if (!mZoomStarted && ((mTotalDelta > 10.0f) || (mTotalDelta < -10.0f))) {
+ mStarted = true;
+ }
+
+ // Ignore short touches in case it is a tap
+ // Also ignore small tilt
+ long time = detector.getEventTime();
+ long interval = time - mBeginTime;
+ if (!mStarted && (interval <= ViewConfiguration.getTapTimeout())) {
+ return false;
+ }
+
+ if (!mStarted) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Get tilt value (scale and clamp)
+ double pitch = getTilt();
+ pitch -= 0.1 * detector.getShovePixelsDelta();
+ pitch = Math.max(MINIMUM_TILT, Math.min(MAXIMUM_TILT, pitch));
+
+ // Tilt the map
+ setTilt(pitch, null);
+
+ return true;
+ }
+ }
+
+ // This class handles input events from the zoom control buttons
+ // Zoom controls allow single touch only devices to zoom in and out
+ private class OnZoomListener implements ZoomButtonsController.OnZoomListener {
+
+ // Not used
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ // Ignore
+ }
+
+ // Called when user pushes a zoom button
+ @Override
+ public void onZoom(boolean zoomIn) {
+ if (!mZoomEnabled) {
+ return;
+ }
+
+ // Zoom in or out
+ zoom(zoomIn);
+ }
+ }
+
+ //
+ // Input events
+ //
+
+ // Called when the user presses a key, also called for repeating keys held
+ // down
+ @Override
+ public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+ // If the user has held the scroll key down for a while then accelerate
+ // the scroll speed
+ double scrollDist = event.getRepeatCount() >= 5 ? 50.0 : 10.0;
+
+ // Check which key was pressed via hardware/real key code
+ switch (keyCode) {
+ // Tell the system to track these keys for long presses on
+ // onKeyLongPress is fired
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ event.startTracking();
+ return true;
+
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Move left
+ mNativeMapView.moveBy(scrollDist / mScreenDensity, 0.0 / mScreenDensity);
+ return true;
+
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Move right
+ mNativeMapView.moveBy(-scrollDist / mScreenDensity, 0.0 / mScreenDensity);
+ return true;
+
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Move up
+ mNativeMapView.moveBy(0.0 / mScreenDensity, scrollDist / mScreenDensity);
+ return true;
+
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Move down
+ mNativeMapView.moveBy(0.0 / mScreenDensity, -scrollDist / mScreenDensity);
+ return true;
+
+ default:
+ // We are not interested in this key
+ return super.onKeyUp(keyCode, event);
+ }
+ }
+
+ // Called when the user long presses a key that is being tracked
+ @Override
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ // Check which key was pressed via hardware/real key code
+ switch (keyCode) {
+ // Tell the system to track these keys for long presses on
+ // onKeyLongPress is fired
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ // Zoom out
+ zoom(false);
+ return true;
+
+ default:
+ // We are not interested in this key
+ return super.onKeyUp(keyCode, event);
+ }
+ }
+
+ // Called when the user releases a key
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ // Check if the key action was canceled (used for virtual keyboards)
+ if (event.isCanceled()) {
+ return super.onKeyUp(keyCode, event);
+ }
+
+ // Check which key was pressed via hardware/real key code
+ // Note if keyboard does not have physical key (ie primary non-shifted
+ // key) then it will not appear here
+ // Must use the key character map as physical to character is not
+ // fixed/guaranteed
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ // Zoom in
+ zoom(true);
+ return true;
+ }
+
+ // We are not interested in this key
+ return super.onKeyUp(keyCode, event);
+ }
+
+ // Called for trackball events, all motions are relative in device specific
+ // units
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ // Choose the action
+ switch (event.getActionMasked()) {
+ // The trackball was rotated
+ case MotionEvent.ACTION_MOVE:
+ if (!mScrollEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Scroll the map
+ mNativeMapView.moveBy(-10.0 * event.getX() / mScreenDensity, -10.0 * event.getY() / mScreenDensity);
+ return true;
+
+ // Trackball was pushed in so start tracking and tell system we are
+ // interested
+ // We will then get the up action
+ case MotionEvent.ACTION_DOWN:
+ // Set up a delayed callback to check if trackball is still
+ // After waiting the system long press time out
+ if (mCurrentTrackballLongPressTimeOut != null) {
+ mCurrentTrackballLongPressTimeOut.cancel();
+ mCurrentTrackballLongPressTimeOut = null;
+ }
+ mCurrentTrackballLongPressTimeOut = new TrackballLongPressTimeOut();
+ postDelayed(mCurrentTrackballLongPressTimeOut,
+ ViewConfiguration.getLongPressTimeout());
+ return true;
+
+ // Trackball was released
+ case MotionEvent.ACTION_UP:
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ // Only handle if we have not already long pressed
+ if (mCurrentTrackballLongPressTimeOut != null) {
+ // Zoom in
+ zoom(true);
+ }
+ return true;
+
+ // Trackball was cancelled
+ case MotionEvent.ACTION_CANCEL:
+ if (mCurrentTrackballLongPressTimeOut != null) {
+ mCurrentTrackballLongPressTimeOut.cancel();
+ mCurrentTrackballLongPressTimeOut = null;
+ }
+ return true;
+
+ default:
+ // We are not interested in this event
+ return super.onTrackballEvent(event);
+ }
+ }
+
+ // This class implements the trackball long press time out callback
+ private class TrackballLongPressTimeOut implements Runnable {
+
+ // Track if we have been cancelled
+ private boolean cancelled;
+
+ public TrackballLongPressTimeOut() {
+ cancelled = false;
+ }
+
+ // Cancel the timeout
+ public void cancel() {
+ cancelled = true;
+ }
+
+ // Called when long press time out expires
+ @Override
+ public void run() {
+ // Check if the trackball is still pressed
+ if (!cancelled) {
+ // Zoom out
+ zoom(false);
+
+ // Ensure the up action is not run
+ mCurrentTrackballLongPressTimeOut = null;
+ }
+ }
+ }
+
+ // Called for events that don't fit the other handlers
+ // such as mouse scroll events, mouse moves, joystick, trackpad
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ // Mouse events
+ //if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { // this is not available before API 18
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == InputDevice.SOURCE_CLASS_POINTER) {
+ // Choose the action
+ switch (event.getActionMasked()) {
+ // Mouse scrolls
+ case MotionEvent.ACTION_SCROLL:
+ if (!mZoomEnabled) {
+ return false;
+ }
+
+ // Cancel any animation
+ mNativeMapView.cancelTransitions();
+
+ // Get the vertical scroll amount, one click = 1
+ float scrollDist = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+
+ // Scale the map by the appropriate power of two factor
+ mNativeMapView.scaleBy(Math.pow(2.0, scrollDist), event.getX() / mScreenDensity, event.getY() / mScreenDensity);
+
+ return true;
+
+ default:
+ // We are not interested in this event
+ return super.onGenericMotionEvent(event);
+ }
+ }
+
+ // We are not interested in this event
+ return super.onGenericMotionEvent(event);
+ }
+
+ // Called when the mouse pointer enters or exits the view
+ // or when it fades in or out due to movement
+ @Override
+ public boolean onHoverEvent(@NonNull MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ case MotionEvent.ACTION_HOVER_MOVE:
+ // Show the zoom controls
+ if (mZoomControlsEnabled && mZoomEnabled) {
+ mZoomButtonsController.setVisible(true);
+ }
+ return true;
+
+ case MotionEvent.ACTION_HOVER_EXIT:
+ // Hide the zoom controls
+ if (mZoomControlsEnabled) {
+ mZoomButtonsController.setVisible(false);
+ }
+
+ default:
+ // We are not interested in this event
+ return super.onHoverEvent(event);
+ }
+ }
+
+ //
+ // Connectivity events
+ //
+
+ // This class handles connectivity changes
+ private class ConnectivityReceiver extends BroadcastReceiver {
+
+ // Called when an action we are listening to in the manifest has been sent
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+ onConnectivityChanged(!noConnectivity);
+ }
+ }
+ }
+
+ // Called when our Internet connectivity has changed
+ private void onConnectivityChanged(boolean isConnected) {
+ mNativeMapView.setReachability(isConnected);
+ }
+
+ //
+ // Map events
+ //
+
+ /**
+ * <p>
+ * Add a callback that's invoked when the displayed map view changes.
+ * </p>
+ * To remove the callback, use {@link MapView#removeOnMapChangedListener(OnMapChangedListener)}.
+ *
+ * @param listener The callback that's invoked on every frame rendered to the map view.
+ * @see MapView#removeOnMapChangedListener(OnMapChangedListener)
+ */
+ @UiThread
+ public void addOnMapChangedListener(@Nullable OnMapChangedListener listener) {
+ if (listener != null) {
+ mOnMapChangedListener.add(listener);
+ }
+ }
+
+ /**
+ * Remove a callback added with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}
+ *
+ * @param listener The previously added callback to remove.
+ * @see MapView#addOnMapChangedListener(OnMapChangedListener)
+ */
+ @UiThread
+ public void removeOnMapChangedListener(@Nullable OnMapChangedListener listener) {
+ if (listener != null) {
+ mOnMapChangedListener.remove(listener);
+ }
+ }
+
+ // Called when the map view transformation has changed
+ // Called via JNI from NativeMapView
+ // Forward to any listeners
+ protected void onMapChanged(int mapChange) {
+ if (mOnMapChangedListener != null) {
+ int count = mOnMapChangedListener.size();
+ for (int i = 0; i < count; i++) {
+ mOnMapChangedListener.get(i).onMapChanged(mapChange);
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Sets a custom renderer for the contents of info window.
+ * </p>
+ * When set your callback is invoked when an info window is about to be shown. By returning
+ * a custom {@link View}, the default info window will be replaced.
+ *
+ * @param infoWindowAdapter The callback to be invoked when an info window will be shown.
+ * To unset the callback, use null.
+ */
+ @UiThread
+ public void setInfoWindowAdapter(@Nullable InfoWindowAdapter infoWindowAdapter) {
+ mInfoWindowAdapter = infoWindowAdapter;
+ }
+
+ /**
+ * Gets the callback to be invoked when an info window will be shown.
+ *
+ * @return The callback to be invoked when an info window will be shown.
+ */
+ @UiThread
+ @Nullable
+ public InfoWindowAdapter getInfoWindowAdapter() {
+ return mInfoWindowAdapter;
+ }
+
+
+ /**
+ * Sets a callback that's invoked on every frame rendered to the map view.
+ *
+ * @param listener The callback that's invoked on every frame rendered to the map view.
+ * To unset the callback, use null.
+ */
+ @UiThread
+ public void setOnFpsChangedListener(@Nullable OnFpsChangedListener listener) {
+ mOnFpsChangedListener = listener;
+ }
+
+ // Called when debug mode is enabled to update a FPS counter
+ // Called via JNI from NativeMapView
+ // Forward to any listener
+ protected void onFpsChanged(final double fps) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (mOnFpsChangedListener != null) {
+ mOnFpsChangedListener.onFpsChanged(fps);
+ }
+ }
+ });
+ }
+
+ /**
+ * Sets a callback that's invoked when the map is scrolled.
+ *
+ * @param listener The callback that's invoked when the map is scrolled.
+ * To unset the callback, use null.
+ */
+ @UiThread
+ public void setOnScrollListener(@Nullable OnScrollListener listener) {
+ mOnScrollListener = listener;
+ }
+
+ /**
+ * Sets a callback that's invoked when the map is flinged.
+ *
+ * @param listener The callback that's invoked when the map is flinged.
+ * To unset the callback, use null.
+ */
+ @UiThread
+ public void setOnFlingListener(@Nullable OnFlingListener listener) {
+ mOnFlingListener = listener;
+ }
+
+ /**
+ * Sets a callback that's invoked when the user clicks on the map view.
+ *
+ * @param listener The callback that's invoked when the user clicks on the map view.
+ * To unset the callback, use null.
+ */
+ @UiThread
+ public void setOnMapClickListener(@Nullable OnMapClickListener listener) {
+ mOnMapClickListener = listener;
+ }
+
+ /**
+ * Sets a callback that's invoked when the user long clicks on the map view.
+ *
+ * @param listener The callback that's invoked when the user long clicks on the map view.
+ * To unset the callback, use null.
+ */
+ @UiThread
+ public void setOnMapLongClickListener(@Nullable OnMapLongClickListener listener) {
+ mOnMapLongClickListener = listener;
+ }
+
+ /**
+ * Sets a callback that's invoked when the user clicks on a marker.
+ *
+ * @param listener The callback that's invoked when the user clicks on a marker.
+ * To unset the callback, use null.
+ */
+ @UiThread
+ public void setOnMarkerClickListener(@Nullable OnMarkerClickListener listener) {
+ mOnMarkerClickListener = listener;
+ }
+
+ /**
+ * Sets a callback that's invoked when the user clicks on an info window.
+ *
+ * @return The callback that's invoked when the user clicks on an info window.
+ */
+ @UiThread
+ @Nullable
+ public OnInfoWindowClickListener getOnInfoWindowClickListener() {
+ return mOnInfoWindowClickListener;
+ }
+
+ /**
+ * Sets a callback that's invoked when the user clicks on an info window.
+ *
+ * @param listener The callback that's invoked when the user clicks on an info window.
+ * To unset the callback, use null.
+ */
+ @UiThread
+ public void setOnInfoWindowClickListener(@Nullable OnInfoWindowClickListener listener) {
+ mOnInfoWindowClickListener = listener;
+ }
+
+ //
+ // User location
+ //
+
+ /**
+ * Returns the status of the my-location layer.
+ *
+ * @return True if the my-location layer is enabled, false otherwise.
+ */
+ @UiThread
+ public boolean isMyLocationEnabled() {
+ return mUserLocationView.isEnabled();
+ }
+
+ /**
+ * <p>
+ * Enables or disables the my-location layer.
+ * While enabled, the my-location layer continuously draws an indication of a user's current
+ * location and bearing.
+ * </p>
+ * In order to use the my-location layer feature you need to request permission for either
+ * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+ * or @link android.Manifest.permission#ACCESS_FINE_LOCATION.
+ *
+ * @param enabled True to enable; false to disable.
+ */
+ @UiThread
+ public void setMyLocationEnabled(boolean enabled) {
+ mUserLocationView.setEnabled(enabled);
+ }
+
+ /**
+ * Returns the currently displayed user location, or null if there is no location data available.
+ *
+ * @return The currently displayed user location.
+ */
+ @UiThread
+ @Nullable
+ public Location getMyLocation() {
+ return mUserLocationView.getLocation();
+ }
+
+ /**
+ * Sets a callback that's invoked when the the My Location dot
+ * (which signifies the user's location) changes location.
+ *
+ * @param listener The callback that's invoked when the user clicks on a marker.
+ * To unset the callback, use null.
+ */
+ @UiThread
+ public void setOnMyLocationChangeListener(@Nullable OnMyLocationChangeListener listener) {
+ mUserLocationView.setOnMyLocationChangeListener(listener);
+ }
+
+ /**
+ * <p>
+ * Set the current my location tracking mode.
+ * Tracking my location disables gestures and pans the viewport
+ * </p>
+ * See {@link MyLocationTracking} for different values.
+ *
+ * @param myLocationTrackingMode The location tracking mode to be used.
+ * @see MyLocationTracking
+ */
+ @UiThread
+ public void setMyLocationTrackingMode(@MyLocationTracking.Mode int myLocationTrackingMode) {
+ mUserLocationView.setMyLocationTrackingMode(myLocationTrackingMode);
+ validateGesturesForTrackingModes();
+ }
+
+ private void validateGesturesForTrackingModes() {
+ int myLocationTrackingMode = mUserLocationView.getMyLocationTrackingMode();
+ int myBearingTrackingMode = mUserLocationView.getMyBearingTrackingMode();
+
+ // Enable/disable gestures based on tracking mode
+ if (myLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
+ mScrollEnabled = true;
+ mRotateEnabled = true;
+ } else {
+ mScrollEnabled = false;
+ mRotateEnabled = (myBearingTrackingMode == MyBearingTracking.NONE);
+ }
+ }
+
+
+ /**
+ * Returns the current user location tracking mode.
+ *
+ * @return The current user location tracking mode.
+ * One of the values from {@link MyLocationTracking.Mode}.
+ * @see MyLocationTracking.Mode
+ */
+ @UiThread
+ @MyLocationTracking.Mode
+ public int getMyLocationTrackingMode() {
+ return mUserLocationView.getMyLocationTrackingMode();
+ }
+
+ /**
+ * <p>
+ * Set the current my bearing tracking mode.
+ * </p>
+ * <p>
+ * Tracking the users bearing will disable gestures and shows the direction the user is heading.
+ * </p>
+ * <p>
+ * When location tracking is disabled the direction of {@link UserLocationView} is rotated
+ * When location tracking is enabled the {@link MapView} is rotated based on bearing value.
+ * </p>
+ * See {@link MyBearingTracking} for different values.
+ *
+ * @param myBearingTrackingMode The bearing tracking mode to be used.
+ * @see MyBearingTracking
+ */
+ @UiThread
+ public void setMyBearingTrackingMode(@MyBearingTracking.Mode int myBearingTrackingMode) {
+ mUserLocationView.setMyBearingTrackingMode(myBearingTrackingMode);
+ validateGesturesForTrackingModes();
+ }
+
+ /**
+ * Returns the current user bearing tracking mode.
+ * See {@link MyBearingTracking} for possible return values.
+ *
+ * @return the current user bearing tracking mode.
+ * @see MyBearingTracking
+ */
+ @UiThread
+ @MyLocationTracking.Mode
+ public int getMyBearingTrackingMode() {
+ //noinspection ResourceType
+ return mUserLocationView.getMyBearingTrackingMode();
+ }
+
+ //
+ // Compass
+ //
+
+ /**
+ * Returns whether the compass is enabled.
+ *
+ * @return True if the compass is enabled; false if the compass is disabled.
+ */
+ @UiThread
+ public boolean isCompassEnabled() {
+ return mCompassView.isEnabled();
+ }
+
+ /**
+ * <p>
+ * Enables or disables the compass. The compass is an icon on the map that indicates the
+ * direction of north on the map. When a user clicks
+ * the compass, the camera orients itself to its default orientation and fades away shortly
+ * after. If disabled, the compass will never be displayed.
+ * </p>
+ * By default, the compass is enabled.
+ *
+ * @param compassEnabled True to enable the compass; false to disable the compass.
+ */
+ @UiThread
+ public void setCompassEnabled(boolean compassEnabled) {
+ mCompassView.setEnabled(compassEnabled);
+ }
+
+ /**
+ * <p>
+ * Sets the gravity of the compass view. Use this to change the corner of the map view that the
+ * compass is displayed in.
+ * </p>
+ * By default, the compass is in the top right corner.
+ *
+ * @param gravity One of the values from {@link Gravity}.
+ * @see Gravity
+ */
+ @UiThread
+ public void setCompassGravity(int gravity) {
+ setWidgetGravity(mCompassView, gravity);
+ }
+
+ /**
+ * Sets the margins of the compass view. Use this to change the distance of the compass from the
+ * map view edge.
+ *
+ * @param left The left margin in pixels.
+ * @param top The top margin in pixels.
+ * @param right The right margin in pixels.
+ * @param bottom The bottom margin in pixels.
+ */
+ @UiThread
+ public void setCompassMargins(int left, int top, int right, int bottom) {
+ setWidgetMargins(mCompassView, left, top, right, bottom);
+ }
+
+ //
+ // Logo
+ //
+
+ /**
+ * <p>
+ * Sets the gravity of the logo view. Use this to change the corner of the map view that the
+ * Mapbox logo is displayed in.
+ * </p>
+ * By default, the logo is in the bottom left corner.
+ *
+ * @param gravity One of the values from {@link Gravity}.
+ * @see Gravity
+ */
+ @UiThread
+ public void setLogoGravity(int gravity) {
+ setWidgetGravity(mLogoView, gravity);
+ }
+
+ /**
+ * Sets the margins of the logo view. Use this to change the distance of the Mapbox logo from the
+ * map view edge.
+ *
+ * @param left The left margin in pixels.
+ * @param top The top margin in pixels.
+ * @param right The right margin in pixels.
+ * @param bottom The bottom margin in pixels.
+ */
+ @UiThread
+ public void setLogoMargins(int left, int top, int right, int bottom) {
+ setWidgetMargins(mLogoView, left, top, right, bottom);
+ }
+
+ /**
+ * <p>
+ * Enables or disables the Mapbox logo.
+ * </p>
+ * By default, the compass is enabled.
+ *
+ * @param visibility True to enable the logo; false to disable the logo.
+ */
+ @UiThread
+ public void setLogoVisibility(int visibility) {
+ mLogoView.setVisibility(visibility);
+ }
+
+ //
+ // Attribution
+ //
+
+ /**
+ * <p>
+ * Sets the gravity of the attribution button view. Use this to change the corner of the map
+ * view that the attribution button is displayed in.
+ * </p>
+ * By default, the attribution button is in the bottom left corner.
+ *
+ * @param gravity One of the values from {@link Gravity}.
+ * @see Gravity
+ */
+ @UiThread
+ public void setAttributionGravity(int gravity) {
+ setWidgetGravity(mAttributionsView, gravity);
+ }
+
+ /**
+ * Sets the margins of the attribution button view. Use this to change the distance of the
+ * attribution button from the map view edge.
+ *
+ * @param left The left margin in pixels.
+ * @param top The top margin in pixels.
+ * @param right The right margin in pixels.
+ * @param bottom The bottom margin in pixels.
+ */
+ @UiThread
+ public void setAttributionMargins(int left, int top, int right, int bottom) {
+ setWidgetMargins(mAttributionsView, left, top, right, bottom);
+ }
+
+ /**
+ * <p>
+ * Enables or disables the attribution button. The attribution is a button with an "i" than when
+ * clicked shows a menu with copyright and legal notices. The menu also inlcudes the "Improve
+ * this map" link which user can report map errors with.
+ * </p>
+ * By default, the attribution button is enabled.
+ *
+ * @param visibility True to enable the attribution button; false to disable the attribution button.
+ */
+ @UiThread
+ public void setAttributionVisibility(int visibility) {
+ mAttributionsView.setVisibility(visibility);
+ }
+
+ @UiThread
+ public void addCustomLayer(CustomLayer customLayer, String before) {
+ mNativeMapView.addCustomLayer(customLayer, before);
+ }
+
+ private void setWidgetGravity(@NonNull final View view, int gravity) {
+ LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
+ layoutParams.gravity = gravity;
+ view.setLayoutParams(layoutParams);
+ }
+
+ private void setWidgetMargins(@NonNull final View view, int left, int top, int right, int bottom) {
+ LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
+ layoutParams.setMargins(left, top, right, bottom);
+ view.setLayoutParams(layoutParams);
+ }
+
+ private void setWidgetMargins(@NonNull final View view, float leftDp, float topDp, float rightDp, float bottomDp) {
+ LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
+ layoutParams.setMargins((int) (leftDp * mScreenDensity), (int) (topDp * mScreenDensity), (int) (rightDp * mScreenDensity), (int) (bottomDp * mScreenDensity));
+ view.setLayoutParams(layoutParams);
+ }
+
+ private static class AttributionOnClickListener implements View.OnClickListener, DialogInterface.OnClickListener {
+
+ private MapView mMapView;
+
+ public AttributionOnClickListener(MapView mapView) {
+ mMapView = mapView;
+ }
+
+ // Called when someone presses the attribution icon
+ @Override
+ public void onClick(View v) {
+ Context context = v.getContext();
+ String[] items = context.getResources().getStringArray(R.array.attribution_names);
+ AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.AttributionAlertDialogStyle);
+ builder.setTitle(R.string.attributionsDialogTitle);
+ builder.setAdapter(new ArrayAdapter<>(context, R.layout.attribution_list_item, items), this);
+ builder.show();
+ }
+
+ // Called when someone selects an attribution, 'Improve this map' adds location data to the url
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Context context = ((Dialog) dialog).getContext();
+ String url = context.getResources().getStringArray(R.array.attribution_links)[which];
+ if (which == ATTRIBUTION_INDEX_IMPROVE_THIS_MAP) {
+ LatLng latLng = mMapView.getCenterCoordinate();
+ url = String.format(url, latLng.getLongitude(), latLng.getLatitude(), (int) mMapView.getZoomLevel());
+ }
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(url));
+ context.startActivity(intent);
+ }
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java
new file mode 100644
index 0000000000..c142670775
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/NativeMapView.java
@@ -0,0 +1,639 @@
+package com.mapbox.mapboxsdk.views;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.view.Surface;
+
+import com.mapbox.mapboxsdk.annotations.Marker;
+import com.mapbox.mapboxsdk.annotations.Polygon;
+import com.mapbox.mapboxsdk.annotations.Polyline;
+import com.mapbox.mapboxsdk.geometry.BoundingBox;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.geometry.LatLngZoom;
+import com.mapbox.mapboxsdk.geometry.ProjectedMeters;
+import com.mapbox.mapboxsdk.layers.CustomLayer;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+// Class that wraps the native methods for convenience
+final class NativeMapView {
+
+ //
+ // Static members
+ //
+
+ //
+ // Instance members
+ //
+
+ boolean mDestroyed = false;
+
+ // Holds the pointer to JNI NativeMapView
+ private long mNativeMapViewPtr = 0;
+
+ // Used for callbacks
+ private WeakReference<MapView> mMapView;
+
+ //
+ // Static methods
+ //
+
+ static {
+ System.loadLibrary("mapbox-gl");
+ }
+
+ //
+ // Constructors
+ //
+
+ public NativeMapView(MapView mapView, String cachePath, String dataPath, String apkPath, float pixelRatio, int availableProcessors, long totalMemory) {
+ if (availableProcessors < 0) {
+ throw new IllegalArgumentException("availableProcessors cannot be negative.");
+ }
+
+ if (totalMemory < 0) {
+ throw new IllegalArgumentException("totalMemory cannot be negative.");
+ }
+
+ mMapView = new WeakReference<>(mapView);
+
+ // Create the NativeMapView
+ mNativeMapViewPtr = nativeCreate(cachePath, dataPath, apkPath, pixelRatio, availableProcessors, totalMemory);
+ }
+
+ //
+ // Methods
+ //
+
+ public void destroy() {
+ nativeDestroy(mNativeMapViewPtr);
+ mNativeMapViewPtr = 0;
+ mMapView = null;
+ mDestroyed = true;
+ }
+
+ public boolean wasDestroyed() {
+ return mDestroyed;
+ }
+
+ 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 pause() {
+ nativePause(mNativeMapViewPtr);
+ }
+
+ public boolean isPaused() {
+ return nativeIsPaused(mNativeMapViewPtr);
+ }
+
+ public void resume() {
+ nativeResume(mNativeMapViewPtr);
+ }
+
+ public void update() {
+ nativeUpdate(mNativeMapViewPtr);
+ }
+
+ public void renderSync() {
+ nativeRenderSync(mNativeMapViewPtr);
+ }
+
+ public void resizeView(int width, int height) {
+ 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.");
+ }
+ nativeViewResize(mNativeMapViewPtr, width, height);
+ }
+
+ public void resizeFramebuffer(int fbWidth, int fbHeight) {
+ 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.");
+ }
+ nativeFramebufferResize(mNativeMapViewPtr, 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(
+ "milliseconds 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 setGestureInProgress(boolean inProgress) {
+ nativeSetGestureInProgress(mNativeMapViewPtr, inProgress);
+ }
+
+ public void moveBy(double dx, double dy) {
+ moveBy(dx, dy, 0);
+ }
+
+ public void moveBy(double dx, double dy, long duration) {
+ nativeMoveBy(mNativeMapViewPtr, dx, dy, duration);
+ }
+
+ public void setLatLng(LatLng latLng) {
+ setLatLng(latLng, 0);
+ }
+
+ public void setLatLng(LatLng latLng, long duration) {
+ nativeSetLatLng(mNativeMapViewPtr, latLng, duration);
+ }
+
+ public LatLng getLatLng() {
+ return nativeGetLatLng(mNativeMapViewPtr);
+ }
+
+ public void resetPosition() {
+ nativeResetPosition(mNativeMapViewPtr);
+ }
+
+ public double getPitch() {
+ return nativeGetPitch(mNativeMapViewPtr);
+ }
+
+ public void setPitch(double pitch, long duration) {
+ nativeSetPitch(mNativeMapViewPtr, pitch, duration);
+ }
+
+ 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);
+ }
+
+ public void scaleBy(double ds, double cx, double cy, long 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);
+ }
+
+ public void setScale(double scale, double cx, double cy, long duration) {
+ nativeSetScale(mNativeMapViewPtr, scale, cx, cy, duration);
+ }
+
+ public double getScale() {
+ return nativeGetScale(mNativeMapViewPtr);
+ }
+
+ public void setZoom(double zoom) {
+ setZoom(zoom, 0);
+ }
+
+ public void setZoom(double zoom, long duration) {
+ nativeSetZoom(mNativeMapViewPtr, zoom, duration);
+ }
+
+ public double getZoom() {
+ return nativeGetZoom(mNativeMapViewPtr);
+ }
+
+ public void setLatLngZoom(LatLngZoom latLngZoom) {
+ setLatLngZoom(latLngZoom, 0);
+ }
+
+ public void setLatLngZoom(LatLngZoom latLngZoom, long duration) {
+ nativeSetLatLngZoom(mNativeMapViewPtr, latLngZoom, duration);
+ }
+
+ public LatLngZoom getLatLngZoom() {
+ return nativeGetLatLngZoom(mNativeMapViewPtr);
+ }
+
+ public void resetZoom() {
+ nativeResetZoom(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);
+ }
+
+ public void rotateBy(double sx, double sy, double ex, double ey,
+ long duration) {
+ nativeRotateBy(mNativeMapViewPtr, sx, sy, ex, ey, duration);
+ }
+
+ public void setBearing(double degrees) {
+ setBearing(degrees, 0);
+ }
+
+ public void setBearing(double degrees, long 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 long addMarker(Marker marker) {
+ return nativeAddMarker(mNativeMapViewPtr, marker);
+ }
+
+ public long[] addMarkers(List<Marker> markers) {
+ return nativeAddMarkers(mNativeMapViewPtr, markers);
+ }
+
+ public long addPolyline(Polyline polyline) {
+ return nativeAddPolyline(mNativeMapViewPtr, polyline);
+ }
+
+ public long[] addPolylines(List<Polyline> polylines) {
+ return nativeAddPolylines(mNativeMapViewPtr, polylines);
+ }
+
+ public long addPolygon(Polygon polygon) {
+ return nativeAddPolygon(mNativeMapViewPtr, polygon);
+ }
+
+ public long[] addPolygons(List<Polygon> polygon) {
+ return nativeAddPolygons(mNativeMapViewPtr, polygon);
+ }
+
+ public void removeAnnotation(long id) {
+ nativeRemoveAnnotation(mNativeMapViewPtr, id);
+ }
+
+ public void removeAnnotations(long[] ids) {
+ nativeRemoveAnnotations(mNativeMapViewPtr, ids);
+ }
+
+ public long[] getAnnotationsInBounds(BoundingBox bbox) {
+ return nativeGetAnnotationsInBounds(mNativeMapViewPtr, bbox);
+ }
+
+ public void addAnnotationIcon(String symbol, int width, int height, float scale, byte[] pixels) {
+ nativeAddAnnotationIcon(mNativeMapViewPtr, symbol, width, height, scale, pixels);
+ }
+
+ public void setVisibleCoordinateBounds(LatLng[] coordinates, RectF padding, double direction, long duration) {
+ nativeSetVisibleCoordinateBounds(mNativeMapViewPtr, coordinates, padding, direction, duration);
+ }
+
+ public void onLowMemory() {
+ nativeOnLowMemory(mNativeMapViewPtr);
+ }
+
+ public void setDebug(boolean debug) {
+ nativeSetDebug(mNativeMapViewPtr, debug);
+ }
+
+ public void cycleDebugOptions() {
+ nativeToggleDebug(mNativeMapViewPtr);
+ }
+
+ public boolean getDebug() {
+ return nativeGetDebug(mNativeMapViewPtr);
+ }
+
+ public boolean isFullyLoaded() {
+ return nativeIsFullyLoaded(mNativeMapViewPtr);
+ }
+
+ public void setReachability(boolean status) {
+ nativeSetReachability(mNativeMapViewPtr, status);
+ }
+
+ public double getMetersPerPixelAtLatitude(double lat, double zoom) {
+ return nativeGetMetersPerPixelAtLatitude(mNativeMapViewPtr, lat, zoom);
+ }
+
+ public ProjectedMeters projectedMetersForLatLng(LatLng latLng) {
+ return nativeProjectedMetersForLatLng(mNativeMapViewPtr, latLng);
+ }
+
+ public LatLng latLngForProjectedMeters(ProjectedMeters projectedMeters) {
+ return nativeLatLngForProjectedMeters(mNativeMapViewPtr, projectedMeters);
+ }
+
+ public PointF pixelForLatLng(LatLng latLng) {
+ return nativePixelForLatLng(mNativeMapViewPtr, latLng);
+ }
+
+ public LatLng latLngForPixel(PointF pixel) {
+ return nativeLatLngForPixel(mNativeMapViewPtr, pixel);
+ }
+
+ public double getTopOffsetPixelsForAnnotationSymbol(String symbolName) {
+ return nativeGetTopOffsetPixelsForAnnotationSymbol(mNativeMapViewPtr, symbolName);
+ }
+
+ public void addCustomLayer(CustomLayer customLayer, String before) {
+ nativeAddCustomLayer(mNativeMapViewPtr, customLayer, before);
+ }
+
+ //
+ // Callbacks
+ //
+
+ protected void onInvalidate() {
+ mMapView.get().onInvalidate();
+ }
+
+ protected void onMapChanged(int rawChange) {
+ mMapView.get().onMapChanged(rawChange);
+ }
+
+ protected void onFpsChanged(double fps) {
+ mMapView.get().onFpsChanged(fps);
+ }
+
+ //
+ // JNI methods
+ //
+
+ private native long nativeCreate(String cachePath, String dataPath, String apkPath, float pixelRatio, int availableProcessors, long totalMemory);
+
+ 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 nativePause(long nativeMapViewPtr);
+
+ private native boolean nativeIsPaused(long nativeMapViewPtr);
+
+ private native void nativeResume(long nativeMapViewPtr);
+
+ private native void nativeUpdate(long nativeMapViewPtr);
+
+ private native void nativeRenderSync(long nativeMapViewPtr);
+
+ private native void nativeViewResize(long nativeMapViewPtr, int width, int height);
+
+ private native void nativeFramebufferResize(long nativeMapViewPtr, 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 duration);
+
+ 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 nativeSetGestureInProgress(long nativeMapViewPtr, boolean inProgress);
+
+ private native void nativeMoveBy(long nativeMapViewPtr, double dx,
+ double dy, long duration);
+
+ private native void nativeSetLatLng(long nativeMapViewPtr, LatLng latLng,
+ long duration);
+
+ private native LatLng nativeGetLatLng(long nativeMapViewPtr);
+
+ private native void nativeResetPosition(long nativeMapViewPtr);
+
+ private native double nativeGetPitch(long nativeMapViewPtr);
+
+ private native void nativeSetPitch(long nativeMapViewPtr, double pitch, long duration);
+
+ private native void nativeScaleBy(long nativeMapViewPtr, double ds,
+ double cx, double cy, long duration);
+
+ private native void nativeSetScale(long nativeMapViewPtr, double scale,
+ double cx, double cy, long duration);
+
+ private native double nativeGetScale(long nativeMapViewPtr);
+
+ private native void nativeSetZoom(long nativeMapViewPtr, double zoom,
+ long duration);
+
+ private native double nativeGetZoom(long nativeMapViewPtr);
+
+ private native void nativeSetLatLngZoom(long nativeMapViewPtr,
+ LatLngZoom lonLatZoom, long duration);
+
+ private native LatLngZoom nativeGetLatLngZoom(long nativeMapViewPtr);
+
+ private native void nativeResetZoom(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, long duration);
+
+ private native void nativeSetBearing(long nativeMapViewPtr, double degrees,
+ long 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 long nativeAddMarker(long nativeMapViewPtr, Marker marker);
+
+ private native long[] nativeAddMarkers(long nativeMapViewPtr, List<Marker> markers);
+
+ private native long nativeAddPolyline(long nativeMapViewPtr, Polyline polyline);
+
+ private native long[] nativeAddPolylines(long mNativeMapViewPtr, List<Polyline> polygon);
+
+ private native long nativeAddPolygon(long mNativeMapViewPtr, Polygon polygon);
+
+ private native long[] nativeAddPolygons(long mNativeMapViewPtr, List<Polygon> polygon);
+
+ private native void nativeRemoveAnnotation(long nativeMapViewPtr, long id);
+
+ private native void nativeRemoveAnnotations(long nativeMapViewPtr, long[] id);
+
+ private native long[] nativeGetAnnotationsInBounds(long mNativeMapViewPtr, BoundingBox bbox);
+
+ private native void nativeAddAnnotationIcon(long nativeMapViewPtr, String symbol,
+ int width, int height, float scale, byte[] pixels);
+
+ private native void nativeSetVisibleCoordinateBounds(long mNativeMapViewPtr, LatLng[] coordinates,
+ RectF padding, double direction, long duration);
+
+ private native void nativeOnLowMemory(long nativeMapViewPtr);
+
+ private native void nativeSetDebug(long nativeMapViewPtr, boolean debug);
+
+ private native void nativeToggleDebug(long nativeMapViewPtr);
+
+ private native boolean nativeGetDebug(long nativeMapViewPtr);
+
+ private native boolean nativeIsFullyLoaded(long nativeMapViewPtr);
+
+ private native void nativeSetReachability(long nativeMapViewPtr, boolean status);
+
+ private native double nativeGetMetersPerPixelAtLatitude(long nativeMapViewPtr, double lat, double zoom);
+
+ private native ProjectedMeters nativeProjectedMetersForLatLng(long nativeMapViewPtr, LatLng latLng);
+
+ private native LatLng nativeLatLngForProjectedMeters(long nativeMapViewPtr, ProjectedMeters projectedMeters);
+
+ private native PointF nativePixelForLatLng(long nativeMapViewPtr, LatLng latLng);
+
+ private native LatLng nativeLatLngForPixel(long nativeMapViewPtr, PointF pixel);
+
+ private native double nativeGetTopOffsetPixelsForAnnotationSymbol(long nativeMapViewPtr, String symbolName);
+
+ private native void nativeAddCustomLayer(long nativeMapViewPtr, CustomLayer customLayer, String before);
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java
new file mode 100644
index 0000000000..ba48eb574f
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java
@@ -0,0 +1,707 @@
+package com.mapbox.mapboxsdk.views;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.location.Location;
+import android.os.Build;
+import android.os.SystemClock;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.mapbox.mapboxsdk.R;
+import com.mapbox.mapboxsdk.constants.MyBearingTracking;
+import com.mapbox.mapboxsdk.constants.MyLocationTracking;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.location.LocationListener;
+import com.mapbox.mapboxsdk.location.LocationServices;
+
+/**
+ * This view shows the user's location, as determined from GPS, on the map
+ * as a dot annotation.
+ */
+final class UserLocationView extends View implements LocationListener {
+
+ private MapView mMapView;
+
+ private float mDensity;
+
+ private boolean mShowMarker;
+ private boolean mShowDirection;
+ private boolean mShowAccuracy;
+ private boolean mStaleMarker;
+
+ private PointF mMarkerScreenPoint;
+ private Matrix mMarkerScreenMatrix;
+
+ private Paint mAccuracyPaintFill;
+ private Paint mAccuracyPaintStroke;
+ private Path mAccuracyPath;
+ private RectF mAccuracyBounds;
+
+ private Drawable mUserLocationDrawable;
+ private RectF mUserLocationDrawableBoundsF;
+ private Rect mUserLocationDrawableBounds;
+
+ private Drawable mUserLocationBearingDrawable;
+ private RectF mUserLocationBearingDrawableBoundsF;
+ private Rect mUserLocationBearingDrawableBounds;
+
+ private Drawable mUserLocationStaleDrawable;
+ private RectF mUserLocationStaleDrawableBoundsF;
+ private Rect mUserLocationStaleDrawableBounds;
+
+ private Rect mDirtyRect;
+ private RectF mDirtyRectF;
+
+ private LatLng mMarkerCoordinate;
+ private ValueAnimator mMarkerCoordinateAnimator;
+ private float mGpsMarkerDirection;
+ private float mCompassMarkerDirection;
+ private ObjectAnimator mMarkerDirectionAnimator;
+ private float mMarkerAccuracy;
+ private ObjectAnimator mMarkerAccuracyAnimator;
+
+ private boolean mPaused = false;
+ private Location mUserLocation;
+
+ MapView.OnMyLocationChangeListener mOnMyLocationChangeListener;
+
+ @MyLocationTracking.Mode
+ private int mMyLocationTrackingMode;
+
+ @MyBearingTracking.Mode
+ private int mMyBearingTrackingMode;
+
+ // Compass data
+ private MyBearingListener mBearingChangeListener;
+
+ public UserLocationView(Context context) {
+ super(context);
+ initialize(context);
+ }
+
+ public UserLocationView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize(context);
+ }
+
+ public UserLocationView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize(context);
+ }
+
+ private void initialize(Context context) {
+ // View configuration
+ setEnabled(false);
+ setWillNotDraw(false);
+
+ // Layout params
+ ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ setLayoutParams(lp);
+
+ // Setup sensors
+ mBearingChangeListener = new MyBearingListener(context);
+
+ // Setup the custom paint
+ Resources resources = context.getResources();
+ int accuracyColor = resources.getColor(R.color.my_location_ring);
+
+ mDensity = resources.getDisplayMetrics().density;
+ mMarkerCoordinate = new LatLng(0.0, 0.0);
+ mMarkerScreenPoint = new PointF();
+ mMarkerScreenMatrix = new Matrix();
+
+ mAccuracyPaintFill = new Paint();
+ mAccuracyPaintFill.setAntiAlias(true);
+ mAccuracyPaintFill.setStyle(Paint.Style.FILL);
+ mAccuracyPaintFill.setColor(accuracyColor);
+ mAccuracyPaintFill.setAlpha((int) (255 * 0.25f));
+
+ mAccuracyPaintStroke = new Paint();
+ mAccuracyPaintStroke.setAntiAlias(true);
+ mAccuracyPaintStroke.setStyle(Paint.Style.STROKE);
+ mAccuracyPaintStroke.setStrokeWidth(0.5f * mDensity);
+ mAccuracyPaintStroke.setColor(accuracyColor);
+ mAccuracyPaintStroke.setAlpha((int) (255 * 0.5f));
+
+ mAccuracyPath = new Path();
+ mAccuracyBounds = new RectF();
+
+ mUserLocationDrawable = ContextCompat.getDrawable(getContext(), R.drawable.my_location);
+ mUserLocationDrawableBounds = new Rect(
+ -mUserLocationDrawable.getIntrinsicWidth() / 2,
+ -mUserLocationDrawable.getIntrinsicHeight() / 2,
+ mUserLocationDrawable.getIntrinsicWidth() / 2,
+ mUserLocationDrawable.getIntrinsicHeight() / 2);
+ mUserLocationDrawableBoundsF = new RectF(
+ -mUserLocationDrawable.getIntrinsicWidth() / 2,
+ -mUserLocationDrawable.getIntrinsicHeight() / 2,
+ mUserLocationDrawable.getIntrinsicWidth() / 2,
+ mUserLocationDrawable.getIntrinsicHeight() / 2);
+ mUserLocationDrawable.setBounds(mUserLocationDrawableBounds);
+
+ mUserLocationBearingDrawable = ContextCompat.getDrawable(getContext(), R.drawable.my_location_bearing);
+ mUserLocationBearingDrawableBounds = new Rect(
+ -mUserLocationBearingDrawable.getIntrinsicWidth() / 2,
+ -mUserLocationBearingDrawable.getIntrinsicHeight() / 2,
+ mUserLocationBearingDrawable.getIntrinsicWidth() / 2,
+ mUserLocationBearingDrawable.getIntrinsicHeight() / 2);
+ mUserLocationBearingDrawableBoundsF = new RectF(
+ -mUserLocationBearingDrawable.getIntrinsicWidth() / 2,
+ -mUserLocationBearingDrawable.getIntrinsicHeight() / 2,
+ mUserLocationBearingDrawable.getIntrinsicWidth() / 2,
+ mUserLocationBearingDrawable.getIntrinsicHeight() / 2);
+ mUserLocationBearingDrawable.setBounds(mUserLocationBearingDrawableBounds);
+
+ mUserLocationStaleDrawable = ContextCompat.getDrawable(getContext(), R.drawable.my_location_stale);
+ mUserLocationStaleDrawableBounds = new Rect(
+ -mUserLocationStaleDrawable.getIntrinsicWidth() / 2,
+ -mUserLocationStaleDrawable.getIntrinsicHeight() / 2,
+ mUserLocationStaleDrawable.getIntrinsicWidth() / 2,
+ mUserLocationStaleDrawable.getIntrinsicHeight() / 2);
+ mUserLocationStaleDrawableBoundsF = new RectF(
+ -mUserLocationStaleDrawable.getIntrinsicWidth() / 2,
+ -mUserLocationStaleDrawable.getIntrinsicHeight() / 2,
+ mUserLocationStaleDrawable.getIntrinsicWidth() / 2,
+ mUserLocationStaleDrawable.getIntrinsicHeight() / 2);
+ mUserLocationStaleDrawable.setBounds(mUserLocationStaleDrawableBounds);
+ }
+
+ public void setMapView(MapView mapView) {
+ mMapView = mapView;
+ }
+
+ public void onStart() {
+ if (mMyBearingTrackingMode == MyBearingTracking.COMPASS) {
+ mBearingChangeListener.onStart(getContext());
+ }
+ }
+
+ public void onStop() {
+ mBearingChangeListener.onStop();
+ cancelAnimations();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (!mShowMarker) {
+ return;
+ }
+
+ canvas.concat(mMarkerScreenMatrix);
+
+ Drawable dotDrawable = mShowDirection ? mUserLocationBearingDrawable : mUserLocationDrawable;
+ dotDrawable = mStaleMarker ? mUserLocationStaleDrawable : dotDrawable;
+ // IMPORTANT also update in update()
+ RectF dotBounds = mShowDirection ? mUserLocationBearingDrawableBoundsF : mUserLocationDrawableBoundsF;
+ dotBounds = mStaleMarker ? mUserLocationStaleDrawableBoundsF : dotBounds;
+
+ boolean willDraw = true;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN || !canvas.isHardwareAccelerated()) {
+ willDraw = mShowAccuracy && !mStaleMarker && !canvas.quickReject(mAccuracyPath, Canvas.EdgeType.AA);
+ }
+ willDraw |= !canvas.quickReject(dotBounds, Canvas.EdgeType.AA);
+
+ if (willDraw) {
+ if (mShowAccuracy && !mStaleMarker) {
+ canvas.drawPath(mAccuracyPath, mAccuracyPaintFill);
+ canvas.drawPath(mAccuracyPath, mAccuracyPaintStroke);
+ }
+ dotDrawable.draw(canvas);
+ }
+ }
+
+ public void setMyLocationTrackingMode(@MyLocationTracking.Mode int myLocationTrackingMode) {
+ mMyLocationTrackingMode = myLocationTrackingMode;
+
+ if (myLocationTrackingMode != MyLocationTracking.TRACKING_NONE && mUserLocation != null) {
+ // center map directly if we have a location fix
+ mMapView.setCenterCoordinate(new LatLng(mUserLocation));
+ }
+ }
+
+ @MyLocationTracking.Mode
+ public int getMyLocationTrackingMode() {
+ return mMyLocationTrackingMode;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+ toggleGps(enabled);
+ }
+
+ public void update() {
+ if (isEnabled() && mShowMarker) {
+ setVisibility(View.VISIBLE);
+
+ mStaleMarker = isStale(mUserLocation);
+
+ // compute new marker position
+ // TODO add JNI method that takes existing pointf
+ if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
+ mMarkerScreenPoint = mMapView.toScreenLocation(mMarkerCoordinate);
+ mMarkerScreenMatrix.reset();
+ mMarkerScreenMatrix.setTranslate(
+ mMarkerScreenPoint.x,
+ mMarkerScreenPoint.y);
+ } else if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW) {
+ mMarkerScreenMatrix.setTranslate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
+ mMapView.setCenterCoordinate(mMarkerCoordinate, true);
+ }
+
+ // rotate so arrow in points to bearing
+ if (mShowDirection) {
+ if (mMyBearingTrackingMode == MyBearingTracking.COMPASS && mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
+ mMarkerScreenMatrix.preRotate(mCompassMarkerDirection + (float) mMapView.getDirection());
+ } else if (mMyBearingTrackingMode == MyBearingTracking.GPS) {
+ if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
+ mMarkerScreenMatrix.preRotate(mGpsMarkerDirection + (float) mMapView.getDirection());
+ } else {
+ mMarkerScreenMatrix.preRotate(mGpsMarkerDirection);
+ }
+ }
+ }
+
+ // adjust accuracy circle
+ if (mShowAccuracy && !mStaleMarker) {
+ mAccuracyPath.reset();
+ mAccuracyPath.addCircle(0.0f, 0.0f,
+ (float) (mMarkerAccuracy / mMapView.getMetersPerPixelAtLatitude(
+ mMarkerCoordinate.getLatitude())),
+ Path.Direction.CW);
+
+ mAccuracyPath.computeBounds(mAccuracyBounds, false);
+ mAccuracyBounds.inset(-1.0f, -1.0f);
+ }
+
+ // invalidate changed pixels
+ if (mDirtyRect == null) {
+ mDirtyRect = new Rect();
+ mDirtyRectF = new RectF();
+ } else {
+ // the old marker location
+ invalidate(mDirtyRect);
+ }
+
+ RectF dotBounds = mShowDirection ? mUserLocationBearingDrawableBoundsF : mUserLocationDrawableBoundsF;
+ dotBounds = mStaleMarker ? mUserLocationStaleDrawableBoundsF : dotBounds;
+ RectF largerBounds = mShowAccuracy && !mStaleMarker && mAccuracyBounds.contains(dotBounds)
+ ? mAccuracyBounds : dotBounds;
+ mMarkerScreenMatrix.mapRect(mDirtyRectF, largerBounds);
+ mDirtyRectF.roundOut(mDirtyRect);
+ invalidate(mDirtyRect); // the new marker location
+ } else {
+ setVisibility(View.INVISIBLE);
+ }
+ }
+
+ public Location getLocation() {
+ return mUserLocation;
+ }
+
+ /**
+ * Enabled / Disable GPS location updates along with updating the UI
+ *
+ * @param enableGps true if GPS is to be enabled, false if GPS is to be disabled
+ */
+ private void toggleGps(boolean enableGps) {
+
+ LocationServices locationServices = LocationServices.getLocationServices(getContext());
+
+ if (enableGps) {
+ // Set an initial location if one available
+ Location lastLocation = locationServices.getLastLocation();
+ if (lastLocation != null) {
+ setLocation(lastLocation);
+ }
+
+ // Register for Location Updates
+ locationServices.addLocationListener(this);
+ } else {
+ // Disable location and user dot
+ setLocation(null);
+
+ // Deregister for Location Updates
+ locationServices.removeLocationListener(this);
+ }
+
+ locationServices.toggleGPS(enableGps);
+ }
+
+ public void setMyBearingTrackingMode(@MyBearingTracking.Mode int myBearingTrackingMode) {
+ mMyBearingTrackingMode = myBearingTrackingMode;
+
+ if (myBearingTrackingMode == MyBearingTracking.COMPASS) {
+ mShowAccuracy = false;
+ mShowDirection = false;
+ mBearingChangeListener.onStart(getContext());
+ } else {
+ mBearingChangeListener.onStop();
+ if (myBearingTrackingMode == MyBearingTracking.GPS) {
+ mShowDirection = (mUserLocation != null) && mUserLocation.hasBearing();
+ } else {
+ mShowDirection = false;
+ }
+ }
+ }
+
+ @MyBearingTracking.Mode
+ public int getMyBearingTrackingMode() {
+ return mMyBearingTrackingMode;
+ }
+
+ private class MyBearingListener implements SensorEventListener {
+
+ private SensorManager mSensorManager;
+ private Sensor mAccelerometer;
+ private Sensor mMagnetometer;
+ private float[] mLastAccelerometer = new float[3];
+ private float[] mLastMagnetometer = new float[3];
+ private boolean mLastAccelerometerSet = false;
+ private boolean mLastMagnetometerSet = false;
+ private float[] mR = new float[9];
+ private float[] mOrientation = new float[3];
+ private float mCurrentDegree = 0f;
+
+ // Controls the sensor update rate in milliseconds
+ private static final int UPDATE_RATE_MS = 300;
+
+ // Compass data
+ private float mCompassBearing;
+ private long mCompassUpdateNextTimestamp = 0;
+
+ public MyBearingListener(Context context) {
+ mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+ }
+
+ public void onStart(Context context) {
+ mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
+ mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME);
+ }
+
+ public void onStop() {
+ mSensorManager.unregisterListener(this, mAccelerometer);
+ mSensorManager.unregisterListener(this, mMagnetometer);
+ }
+
+ public float getCompassBearing() {
+ return mCompassBearing;
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (mPaused) {
+ return;
+ }
+
+ long currentTime = SystemClock.elapsedRealtime();
+ if (currentTime < mCompassUpdateNextTimestamp) {
+ return;
+ }
+
+ if (event.sensor == mAccelerometer) {
+ System.arraycopy(event.values, 0, mLastAccelerometer, 0, event.values.length);
+ mLastAccelerometerSet = true;
+ } else if (event.sensor == mMagnetometer) {
+ System.arraycopy(event.values, 0, mLastMagnetometer, 0, event.values.length);
+ mLastMagnetometerSet = true;
+ }
+
+ if (mLastAccelerometerSet && mLastMagnetometerSet) {
+ SensorManager.getRotationMatrix(mR, null, mLastAccelerometer, mLastMagnetometer);
+ SensorManager.getOrientation(mR, mOrientation);
+ float azimuthInRadians = mOrientation[0];
+ float azimuthInDegress = (float) (Math.toDegrees(azimuthInRadians) + 360) % 360;
+ mCompassBearing = mCurrentDegree;
+ mCurrentDegree = -azimuthInDegress;
+ }
+ mCompassUpdateNextTimestamp = currentTime + UPDATE_RATE_MS;
+ setCompass(mCompassBearing);
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // TODO add accuracy to the equiation
+ }
+ }
+
+ /**
+ * Callback method for receiving location updates from LocationServices.
+ *
+ * @param location The new Location data
+ */
+ @Override
+ public void onLocationChanged(Location location) {
+ if (mPaused) {
+ return;
+ }
+ setLocation(location);
+ }
+
+ private boolean isStale(Location location) {
+ if (location != null) {
+ long ageInNanos;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ ageInNanos = SystemClock.elapsedRealtimeNanos() -
+ location.getElapsedRealtimeNanos();
+ } else {
+ ageInNanos = (System.currentTimeMillis() - location.getTime()) * 1000 * 1000;
+ }
+ final long oneMinuteInNanos = 60L * 1000 * 1000 * 1000;
+ return ageInNanos > oneMinuteInNanos;
+ } else {
+ return false;
+ }
+ }
+
+ // Handles location updates from GPS
+ private void setLocation(Location location) {
+ // if null we should hide the marker
+ if (location == null) {
+ mShowMarker = false;
+ mShowDirection = false;
+ mShowAccuracy = false;
+
+ cancelAnimations();
+
+ mUserLocation = null;
+ return;
+ }
+
+ if (mMarkerCoordinateAnimator != null) {
+ mMarkerCoordinateAnimator.end();
+ mMarkerCoordinateAnimator = null;
+ }
+
+ if (mMarkerDirectionAnimator != null) {
+ mMarkerDirectionAnimator.end();
+ mMarkerDirectionAnimator = null;
+ }
+
+ if (mMarkerAccuracyAnimator != null) {
+ mMarkerAccuracyAnimator.end();
+ mMarkerAccuracyAnimator = null;
+ }
+
+ mShowMarker = true;
+
+ LatLng previousCoordinate;
+ if (mUserLocation == null) {
+ previousCoordinate = new LatLng(location);
+ } else {
+ previousCoordinate = new LatLng(mUserLocation);
+ }
+
+ if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
+ // moving marker above map
+ mMarkerCoordinateAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mMarkerCoordinateAnimator.setDuration(1000);
+ mMarkerCoordinateAnimator.addUpdateListener(new MarkerCoordinateAnimatorListener(
+ previousCoordinate, new LatLng(location)
+ ));
+ mMarkerCoordinateAnimator.start();
+ } else {
+ // moving map under the tracker
+ mMarkerCoordinate = new LatLng(location);
+ mMapView.setCenterCoordinate(mMarkerCoordinate, true);
+ }
+
+ if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE && mMyBearingTrackingMode == MyBearingTracking.GPS) {
+ // show GPS direction
+ mShowDirection = location.hasBearing();
+ if (mShowDirection) {
+ if (mUserLocation != null && mUserLocation.hasBearing()) {
+ mGpsMarkerDirection = mUserLocation.getBearing();
+ }
+ float oldDir = mGpsMarkerDirection;
+ float newDir = location.getBearing();
+ float diff = oldDir - newDir;
+ if (diff > 180.0f) {
+ newDir += 360.0f;
+ } else if (diff < -180.0f) {
+ newDir -= 360.f;
+ }
+ mMarkerDirectionAnimator = ObjectAnimator.ofFloat(this, "direction", oldDir, newDir);
+ mMarkerDirectionAnimator.setDuration(1000);
+ mMarkerDirectionAnimator.start();
+ }
+ } else if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW && mMyBearingTrackingMode == MyBearingTracking.GPS) {
+ // always show north & rotate map below
+ mShowDirection = true;
+ mGpsMarkerDirection = 0;
+ if (location.hasBearing()) {
+ mMapView.setBearing(location.getBearing());
+ }
+ }
+
+ mShowAccuracy = location.hasAccuracy();
+ if (mShowAccuracy) {
+ if (mUserLocation != null && mUserLocation.hasAccuracy()) {
+ mMarkerAccuracy = mUserLocation.getAccuracy();
+ }
+ mMarkerAccuracyAnimator = ObjectAnimator.ofFloat(this, "accuracy", location.getAccuracy());
+ mMarkerAccuracyAnimator.setDuration(1000);
+ mMarkerAccuracyAnimator.start();
+ }
+
+ mUserLocation = location;
+ updateOnNextFrame();
+
+ if (mOnMyLocationChangeListener != null) {
+ mOnMyLocationChangeListener.onMyLocationChange(location);
+ }
+ }
+
+ // handles compass sensor updates
+ private void setCompass(float bearing) {
+ if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
+ // animate marker
+ mShowDirection = true;
+ float oldDir = mCompassMarkerDirection;
+ float newDir = bearing;
+ float diff = oldDir - newDir;
+ if (diff > 180.0f) {
+ newDir += 360.0f;
+ } else if (diff < -180.0f) {
+ newDir -= 360.f;
+ }
+ mMarkerDirectionAnimator = ObjectAnimator.ofFloat(this, "direction", oldDir, newDir);
+ mMarkerDirectionAnimator.setDuration(1000);
+ mMarkerDirectionAnimator.start();
+ mCompassMarkerDirection = bearing;
+
+ } else if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW) {
+ cancelAnimations();
+ if (mMyBearingTrackingMode == MyBearingTracking.COMPASS) {
+ // always show north & change map direction
+ mShowDirection = true;
+ mGpsMarkerDirection = 0;
+ mCompassMarkerDirection = 0;
+ mMapView.setBearing(bearing);
+ }
+ }
+ }
+
+ void updateOnNextFrame() {
+ mMapView.update();
+ }
+
+ /**
+ * Called from MapView.onPause()
+ */
+ public void pause() {
+ mPaused = true;
+ toggleGps(false);
+ }
+
+ /**
+ * Called from MapView.onResume()
+ */
+ public void resume() {
+ mPaused = false;
+ if (isEnabled()) {
+ toggleGps(true);
+ }
+ }
+
+ public void setOnMyLocationChangeListener(@Nullable MapView.OnMyLocationChangeListener listener) {
+ mOnMyLocationChangeListener = listener;
+ }
+
+ // public for animator only
+ public float getDirection() {
+ if (mMyBearingTrackingMode == MyBearingTracking.COMPASS) {
+ return mCompassMarkerDirection;
+ }
+ return mGpsMarkerDirection;
+ }
+
+ // public for animator only
+ public void setDirection(float direction) {
+ if (mMyBearingTrackingMode == MyBearingTracking.COMPASS) {
+ mCompassMarkerDirection = direction % 360.0f;
+ } else {
+ mGpsMarkerDirection = direction % 360.0f;
+ }
+ updateOnNextFrame();
+ }
+
+ // public for animator only
+ public float getAccuracy() {
+ return mMarkerAccuracy;
+ }
+
+ // public for animator only
+ public void setAccuracy(float accuracy) {
+ mMarkerAccuracy = accuracy;
+ updateOnNextFrame();
+ }
+
+ private class MarkerCoordinateAnimatorListener implements ValueAnimator.AnimatorUpdateListener {
+
+ private double mFromLat;
+ private double mFromLng;
+ private double mToLat;
+ private double mToLng;
+
+ private MarkerCoordinateAnimatorListener(LatLng from, LatLng to) {
+ mFromLat = from.getLatitude();
+ mFromLng = from.getLongitude();
+ mToLat = to.getLatitude();
+ mToLng = to.getLongitude();
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float frac = animation.getAnimatedFraction();
+ double latitude = mFromLat + (mToLat - mFromLat) * frac;
+ double longitude = mFromLng + (mToLng - mFromLng) * frac;
+ mMarkerCoordinate.setLatitude(latitude);
+ mMarkerCoordinate.setLongitude(longitude);
+ updateOnNextFrame();
+ }
+ }
+
+ public void cancelAnimations() {
+ if (mMarkerCoordinateAnimator != null) {
+ mMarkerCoordinateAnimator.cancel();
+ mMarkerCoordinateAnimator = null;
+ }
+
+ if (mMarkerDirectionAnimator != null) {
+ mMarkerDirectionAnimator.cancel();
+ mMarkerDirectionAnimator = null;
+ }
+
+ if (mMarkerAccuracyAnimator != null) {
+ mMarkerAccuracyAnimator.cancel();
+ mMarkerAccuracyAnimator = null;
+ }
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/package-info.java
new file mode 100644
index 0000000000..a17f268c01
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * This package contains the {@link com.mapbox.mapboxsdk.views.MapView} and related classes.
+ * {@code MapView} is the core component of this SDK and adds a map to your app.
+ */
+package com.mapbox.mapboxsdk.views;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/overview.html b/platform/android/MapboxGLAndroidSDK/src/main/java/overview.html
new file mode 100644
index 0000000000..951b8d8e75
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/overview.html
@@ -0,0 +1,17 @@
+<html>
+<body>
+An open source OpenGL-based vector map solution for Android with full styling capabilities.
+<p/>
+For more information, check out <a href="https://www.mapbox.com/android-sdk/">our online overview</a>.
+<p/>
+<!--TODO: Need an Android image-->
+<!--<img src="https://raw.githubusercontent.com/mapbox/mapbox-gl-native/master/ios/screenshot.png"/>-->
+<!--<h2>Changelog</h2>
+<h3>2.1.0</h3>
+Initial release.
+<h3>2.0.0</h3>
+Initial preview release.
+<h3>0.1.0</h3>
+<p>Initial beta release.</p>-->
+</body>
+</html> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml
new file mode 100644
index 0000000000..777d879d48
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!--Add references to exposed resources-->
+ <public name="AttributionAlertDialogStyle" type="style" />
+
+ <public name="style_mapbox_streets" type="string" />
+ <public name="style_emerald" type="string" />
+ <public name="style_light" type="string" />
+ <public name="style_dark" type="string" />
+ <public name="style_satellite" type="string" />
+ <public name="style_satellite_streets" type="string" />
+
+ <public name="center_longitude" type="attr" />
+ <public name="center_latitude" type="attr" />
+ <public name="zoom_level" type="attr" />
+ <public name="direction" type="attr" />
+ <public name="zoom_enabled" type="attr" />
+ <public name="scroll_enabled" type="attr" />
+ <public name="rotate_enabled" type="attr" />
+ <public name="zoom_controls_enabled" type="attr" />
+ <public name="debug_active" type="attr" />
+ <public name="style_url" type="attr" />
+ <public name="access_token" type="attr" />
+ <public name="style_classes" type="attr" />
+ <public name="my_location_enabled" type="attr" />
+ <public name="compass_enabled" type="attr" />
+ <public name="compass_gravity" type="attr" />
+ <public name="compass_margin_left" type="attr" />
+ <public name="compass_margin_top" type="attr" />
+ <public name="compass_margin_right" type="attr" />
+ <public name="compass_margin_bottom" type="attr" />
+ <public name="logo_gravity" type="attr" />
+ <public name="logo_margin_left" type="attr" />
+ <public name="logo_margin_top" type="attr" />
+ <public name="logo_margin_right" type="attr" />
+ <public name="logo_margin_bottom" type="attr" />
+ <public name="logo_visibility" type="attr" />
+ <public name="attribution_gravity" type="attr" />
+ <public name="attribution_margin_left" type="attr" />
+ <public name="attribution_margin_top" type="attr" />
+ <public name="attribution_margin_right" type="attr" />
+ <public name="attribution_margin_bottom" type="attr" />
+ <public name="attribution_visibility" type="attr" />
+
+ <public name="attribution_logo" type="drawable" />
+ <public name="compass" type="drawable" />
+ <public name="default_marker" type="drawable" />
+ <public name="attribution_button_pressed_selector" type="drawable" />
+ <public name="attribution_button_pressed_normal" type="drawable" />
+ <public name="attribution_button_pressed_pressed" type="drawable" />
+ <public name="my_location" type="drawable" />
+ <public name="my_location_bearing" type="drawable" />
+ <public name="my_location_stale" type="drawable" />
+ <public name="my_location_ring" type="color" />
+ <public name="mapbox_blue" type="color" />
+</resources>
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/color/material_bg_selector.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/color/material_bg_selector.xml
new file mode 100644
index 0000000000..64d7f46c2d
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/color/material_bg_selector.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?attr/colorPrimaryDark" android:state_pressed="true" />
+ <item android:color="?attr/colorPrimary" />
+</selector> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/attribution_logo.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/attribution_logo.png
new file mode 100644
index 0000000000..c0f4ed2c4c
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/attribution_logo.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/compass.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/compass.png
new file mode 100644
index 0000000000..6bb20027f4
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/compass.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/default_marker.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/default_marker.png
new file mode 100644
index 0000000000..8b0af4c6ab
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/default_marker.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/my_location.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/my_location.png
new file mode 100755
index 0000000000..1ae8d541af
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/my_location.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/my_location_bearing.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/my_location_bearing.png
new file mode 100755
index 0000000000..8ecaffa2e8
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/my_location_bearing.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/my_location_stale.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/my_location_stale.png
new file mode 100755
index 0000000000..0d599c01fa
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/my_location_stale.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/attribution_logo.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/attribution_logo.png
new file mode 100644
index 0000000000..5a9da3fe39
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/attribution_logo.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/compass.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/compass.png
new file mode 100644
index 0000000000..cf15ed1876
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/compass.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/default_marker.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/default_marker.png
new file mode 100644
index 0000000000..b112096c18
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/default_marker.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/my_location.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/my_location.png
new file mode 100755
index 0000000000..542cd25e22
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/my_location.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/my_location_bearing.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/my_location_bearing.png
new file mode 100755
index 0000000000..429f03f648
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/my_location_bearing.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/my_location_stale.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/my_location_stale.png
new file mode 100755
index 0000000000..6613c41153
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/my_location_stale.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-v21/bg_default_selector.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-v21/bg_default_selector.xml
new file mode 100644
index 0000000000..ef82c18f5d
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-v21/bg_default_selector.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="oval">
+ <solid android:color="@android:color/white" />
+ </shape>
+ </item>
+</ripple> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/attribution_logo.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/attribution_logo.png
new file mode 100644
index 0000000000..194aa64da2
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/attribution_logo.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/compass.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/compass.png
new file mode 100644
index 0000000000..6257209368
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/compass.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/default_marker.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/default_marker.png
new file mode 100644
index 0000000000..d05c82bfe2
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/default_marker.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/my_location.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/my_location.png
new file mode 100755
index 0000000000..ca1f1fe630
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/my_location.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/my_location_bearing.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/my_location_bearing.png
new file mode 100755
index 0000000000..1b88f9f489
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/my_location_bearing.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/my_location_stale.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/my_location_stale.png
new file mode 100755
index 0000000000..7af3789ff0
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/my_location_stale.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/attribution_logo.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/attribution_logo.png
new file mode 100644
index 0000000000..d1260a16f3
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/attribution_logo.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/compass.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/compass.png
new file mode 100644
index 0000000000..c59b4c7528
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/compass.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/default_marker.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/default_marker.png
new file mode 100644
index 0000000000..703b172c15
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/default_marker.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/my_location.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/my_location.png
new file mode 100755
index 0000000000..6f175df168
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/my_location.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/my_location_bearing.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/my_location_bearing.png
new file mode 100755
index 0000000000..f4bb454a06
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/my_location_bearing.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/my_location_stale.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/my_location_stale.png
new file mode 100755
index 0000000000..f1d2f2eca0
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/my_location_stale.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/attribution_logo.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/attribution_logo.png
new file mode 100644
index 0000000000..5f9647610a
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/attribution_logo.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/bg_infowindow_content.9.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/bg_infowindow_content.9.png
new file mode 100644
index 0000000000..584b320299
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/bg_infowindow_content.9.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/compass.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/compass.png
new file mode 100755
index 0000000000..da04f5b94c
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/compass.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/default_marker.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/default_marker.png
new file mode 100644
index 0000000000..8331ffef71
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/default_marker.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/my_location.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/my_location.png
new file mode 100755
index 0000000000..d43541ac3c
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/my_location.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/my_location_bearing.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/my_location_bearing.png
new file mode 100755
index 0000000000..a8cccbb3e2
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/my_location_bearing.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/my_location_stale.png b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/my_location_stale.png
new file mode 100755
index 0000000000..33e952391f
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/my_location_stale.png
Binary files differ
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/bg_default_selector.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/bg_default_selector.xml
new file mode 100644
index 0000000000..48e53e7ee8
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/bg_default_selector.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@color/gray_light" android:state_pressed="true" />
+ <item android:drawable="@color/gray_light" android:state_focused="true" />
+ <item android:drawable="@android:color/transparent" />
+</selector> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_outline_24dp.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_outline_24dp.xml
new file mode 100644
index 0000000000..fa82bb8d9b
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_outline_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF1E8CAB"
+ android:pathData="M11,17h2v-6h-2v6zm1,-15C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2V7h-2v2z"/>
+</vector>
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_outline_24dp_selected.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_outline_24dp_selected.xml
new file mode 100644
index 0000000000..074928d05a
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_outline_24dp_selected.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0">
+ <path
+ android:fillColor="#551E8CAB"
+ android:pathData="M11,17h2v-6h-2v6zm1,-15C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2V7h-2v2z" />
+</vector>
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_outline_24dp_selector.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_outline_24dp_selector.xml
new file mode 100644
index 0000000000..7f6245b30a
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_outline_24dp_selector.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/ic_info_outline_24dp_selected" android:state_pressed="true" />
+ <item android:drawable="@drawable/ic_info_outline_24dp" />
+</selector> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_list_item.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_list_item.xml
new file mode 100644
index 0000000000..1252fb194f
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_list_item.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall"
+ android:textColor="@android:color/white" />
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/fragment_mapview.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/fragment_mapview.xml
new file mode 100644
index 0000000000..22afd6e513
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/fragment_mapview.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.mapbox.mapboxsdk.views.MapView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/infowindow_content.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/infowindow_content.xml
new file mode 100644
index 0000000000..3e36cbf91a
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/infowindow_content.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <LinearLayout
+ android:id="@+id/infowindow_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/bg_infowindow_content"
+ android:orientation="vertical"
+ android:paddingBottom="16dp"
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:paddingTop="14dp">
+
+ <TextView
+ android:id="@+id/infowindow_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="2dp"
+ android:maxEms="17"
+ android:text="@string/infoWindowTitle"
+ android:textColor="@color/black"
+ android:textSize="18sp"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/infowindow_description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="2dp"
+ android:layout_marginTop="2dp"
+ android:lineSpacingExtra="1dp"
+ android:maxEms="17"
+ android:text="@string/infoWindowDescription"
+ android:textColor="@color/gray"
+ android:textSize="14sp" />
+
+ <TextView
+ android:id="@+id/infowindow_subdescription"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxEms="17"
+ android:text="@string/infoWindowAddress"
+ android:textColor="@color/black"
+ android:textSize="12sp"
+ android:visibility="gone" />
+ </LinearLayout>
+
+ <com.mapbox.mapboxsdk.annotations.InfoWindowTipView
+ android:id="@+id/infowindow_tipview"
+ android:layout_width="@dimen/infowindow_tipview_width"
+ android:layout_height="14dp"
+ android:layout_below="@+id/infowindow_content" />
+
+</merge>
+
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/infowindow_view.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/infowindow_view.xml
new file mode 100644
index 0000000000..ff47642426
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/infowindow_view.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.mapbox.mapboxsdk.annotations.InfoWindowView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapview_internal.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapview_internal.xml
new file mode 100644
index 0000000000..a7503332a4
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapview_internal.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <TextureView
+ android:id="@+id/textureView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <com.mapbox.mapboxsdk.views.CompassView
+ android:id="@+id/compassView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageView
+ android:id="@+id/logoView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/mapboxIconContentDescription"
+ android:src="@drawable/attribution_logo" />
+
+ <ImageView
+ android:id="@+id/attributionView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:adjustViewBounds="true"
+ android:clickable="true"
+ android:contentDescription="@string/attributionsIconContentDescription"
+ android:padding="7dp"
+ android:src="@drawable/ic_info_outline_24dp_selector"
+ android:background="@drawable/bg_default_selector"/>
+
+ <com.mapbox.mapboxsdk.views.UserLocationView
+ android:id="@+id/userLocationView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</merge> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml
new file mode 100644
index 0000000000..2c1fdf8d13
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <array name="attribution_names">
+ <item>&#169; Mapbox</item>
+ <item>&#169; OpenStreetMap</item>
+ <item>Improve this map</item>
+ </array>
+ <!-- If editing this array update MapView.ATTRIBUTION_INDEX_IMPROVE_THIS_MAP -->
+ <array name="attribution_links" formatted="false" translatable="false">
+ <item>https://www.mapbox.com/about/maps/</item>
+ <item>http://www.openstreetmap.org/about/</item>
+ <item>https://www.mapbox.com/map-feedback/#/%1$f/%2$f/%3$d</item>
+ </array>
+</resources> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml
new file mode 100644
index 0000000000..35696850b7
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml
@@ -0,0 +1,90 @@
+<resources>
+ <!-- these are public -->
+ <declare-styleable name="MapView">
+ <attr name="center_longitude" format="float" />
+ <attr name="center_latitude" format="float" />
+ <attr name="zoom_level" format="float" />
+ <attr name="direction" format="float" />
+ <attr name="zoom_enabled" format="boolean" />
+ <attr name="scroll_enabled" format="boolean" />
+ <attr name="rotate_enabled" format="boolean" />
+ <attr name="tilt_enabled" format="boolean" />
+ <attr name="zoom_controls_enabled" format="boolean" />
+ <attr name="debug_active" format="boolean" />
+ <attr name="style_url" format="string" />
+ <attr name="access_token" format="string" />
+ <attr name="style_classes" format="string" />
+ <attr name="my_location_enabled" format="boolean" />
+ <attr name="compass_enabled" format="boolean" />
+ <attr name="compass_gravity">
+ <flag name="top" value="0x30" />
+ <flag name="bottom" value="0x50" />
+ <flag name="left" value="0x03" />
+ <flag name="right" value="0x05" />
+ <flag name="center_vertical" value="0x10" />
+ <flag name="fill_vertical" value="0x70" />
+ <flag name="center_horizontal" value="0x01" />
+ <flag name="fill_horizontal" value="0x07" />
+ <flag name="center" value="0x11" />
+ <flag name="fill" value="0x77" />
+ <flag name="clip_vertical" value="0x80" />
+ <flag name="clip_horizontal" value="0x08" />
+ <flag name="start" value="0x00800003" />
+ <flag name="end" value="0x00800005" />
+ </attr>
+ <attr name="compass_margin_left" format="dimension" />
+ <attr name="compass_margin_top" format="dimension" />
+ <attr name="compass_margin_right" format="dimension" />
+ <attr name="compass_margin_bottom" format="dimension" />
+ <attr name="logo_gravity">
+ <flag name="top" value="0x30" />
+ <flag name="bottom" value="0x50" />
+ <flag name="left" value="0x03" />
+ <flag name="right" value="0x05" />
+ <flag name="center_vertical" value="0x10" />
+ <flag name="fill_vertical" value="0x70" />
+ <flag name="center_horizontal" value="0x01" />
+ <flag name="fill_horizontal" value="0x07" />
+ <flag name="center" value="0x11" />
+ <flag name="fill" value="0x77" />
+ <flag name="clip_vertical" value="0x80" />
+ <flag name="clip_horizontal" value="0x08" />
+ <flag name="start" value="0x00800003" />
+ <flag name="end" value="0x00800005" />
+ </attr>
+ <attr name="logo_margin_left" format="dimension" />
+ <attr name="logo_margin_top" format="dimension" />
+ <attr name="logo_margin_right" format="dimension" />
+ <attr name="logo_margin_bottom" format="dimension" />
+ <attr name="logo_visibility">
+ <enum name="visible" value="0x0" />
+ <enum name="invisible" value="0x4" />
+ <enum name="gone" value="0x8" />
+ </attr>
+ <attr name="attribution_gravity">
+ <flag name="top" value="0x30" />
+ <flag name="bottom" value="0x50" />
+ <flag name="left" value="0x03" />
+ <flag name="right" value="0x05" />
+ <flag name="center_vertical" value="0x10" />
+ <flag name="fill_vertical" value="0x70" />
+ <flag name="center_horizontal" value="0x01" />
+ <flag name="fill_horizontal" value="0x07" />
+ <flag name="center" value="0x11" />
+ <flag name="fill" value="0x77" />
+ <flag name="clip_vertical" value="0x80" />
+ <flag name="clip_horizontal" value="0x08" />
+ <flag name="start" value="0x00800003" />
+ <flag name="end" value="0x00800005" />
+ </attr>
+ <attr name="attribution_margin_left" format="dimension" />
+ <attr name="attribution_margin_top" format="dimension" />
+ <attr name="attribution_margin_right" format="dimension" />
+ <attr name="attribution_margin_bottom" format="dimension" />
+ <attr name="attribution_visibility">
+ <enum name="visible" value="0x0" />
+ <enum name="invisible" value="0x4" />
+ <enum name="gone" value="0x8" />
+ </attr>
+ </declare-styleable>
+</resources>
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml
new file mode 100644
index 0000000000..97d29c7d45
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="white">#FFFFFF</color>
+ <color name="black">#000000</color>
+ <color name="gray">#7D7F80</color>
+ <color name="gray_light">#EEEEEE</color>
+ <color name="mapbox_blue">#1E8CAB</color>
+ <color name="my_location_ring">@color/mapbox_blue</color>
+</resources>
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml
new file mode 100644
index 0000000000..dc09cc1114
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <dimen name="infowindow_tipview_width">20dp</dimen>
+ <dimen name="infowindow_margin">8dp</dimen>
+ <dimen name="infowindow_offset">-2dp</dimen>
+ <dimen name="infowindow_line_width">1.5dp</dimen>
+</resources> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..eadcdcc043
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="compassContentDescription">Map compass. Click to reset the map rotation to North.</string>
+ <string name="attributionsIconContentDescription">Attribution icon. Click to show attribution dialog.</string>
+ <string name="attributionsDialogTitle">Mapbox Android SDK</string>
+ <string name="mapboxIconContentDescription">The Mapbox logo.</string>
+ <string name="infoWindowTitle">Title</string>
+ <string name="infoWindowDescription">Description</string>
+ <string name="infoWindowAddress">Address</string>
+
+ <!-- these are public -->
+ <string name="style_mapbox_streets">mapbox://styles/mapbox/streets-v8</string>
+ <string name="style_emerald">mapbox://styles/mapbox/emerald-v8</string>
+ <string name="style_light">mapbox://styles/mapbox/light-v8</string>
+ <string name="style_dark">mapbox://styles/mapbox/dark-v8</string>
+ <string name="style_satellite">mapbox://styles/mapbox/satellite-v8</string>
+ <string name="style_satellite_streets">mapbox://styles/mapbox/satellite-hybrid-v8</string>
+</resources>
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml
new file mode 100644
index 0000000000..9ad12f76ad
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <!-- these are public -->
+ <style name="AttributionAlertDialogStyle" parent="Theme.AppCompat.Dialog.Alert">
+ <item name="android:textColorPrimary">@android:color/white</item>
+ <item name="android:background">@color/mapbox_blue</item>
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties b/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties
new file mode 100644
index 0000000000..0031dc8cc8
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties
@@ -0,0 +1,3 @@
+fabric-identifier=com.mapbox.mapboxsdk.mapbox-android-sdk
+fabric-version=2.3.0
+fabric-build-type=binary