From 1de098e66fa8d5feed217908010d0b623b624c18 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 4 Mar 2024 21:44:44 +0100 Subject: [PATCH] Add render tests to Gradle project root at `platform/android` (#2162) --- platform/android/DEVELOPING.md | 169 ++++++------------ platform/android/Makefile | 2 +- .../android/offline/OfflineManager.kt | 56 +----- .../android/maps/MapGestureDetectorTest.kt | 2 +- .../src/main/res/values/strings.xml | 2 +- platform/android/gradle/publish-root.gradle | 25 +-- platform/android/settings.gradle | 5 +- render-test/android/app/build.gradle | 2 +- render-test/android/build.gradle | 2 +- render-test/android/settings.gradle | 3 +- 10 files changed, 75 insertions(+), 193 deletions(-) diff --git a/platform/android/DEVELOPING.md b/platform/android/DEVELOPING.md index 0e0135af1..50222a13f 100644 --- a/platform/android/DEVELOPING.md +++ b/platform/android/DEVELOPING.md @@ -1,12 +1,65 @@ # Developing - MapLibre Native for Android -These instructions are for developers interested in making code-level contributions to the SDK itself. +These instructions are for developers interested in making code-level contributions to MapLibre Native for Android. + +## Getting the source + +Clone the git repository and pull in submodules: + +```bash +git clone git@github.com:maplibre/maplibre-native.git +cd maplibre-native +git submodule update --init --recursive +cd platform/android +``` + +## Requirements + +[Android Studio](https://developer.android.com/studio) needs to be installed. + +Open the `platform/android` directory to get started. + +## Setting an API Key + +_The test application (used for development purposes) uses MapTiler vector tiles, which require a MapTiler account and API key._ + +With the first Gradle invocation, Gradle will take the value of the `MLN_API_KEY` environment variable and save it to `MapboxGLAndroidSDKTestApp/src/main/res/values/developer-config.xml`. If the environment variable wasn't set, you can edit `developer-config.xml` manually and add your API key to the `api_key` resource. + +## Running the TestApp + +Run the configuration for the `MapboxGLAndroidSDKTestApp` module and select a device or emulator to deploy on. + +

+ Android TestApp menu Android TestApp showing Demotiles +

+ +## Render Tests + +To run the render tests for Android, run the configuration for the `androidRenderTest.app` module. + +## Instrumentation Tests + +To run the instrumentatin tests, find the `MapboxGLAndroidSDKTestApp` project in the sidebar, go to `src > androidTest`, right click and select "Run 'All Tests'". + +image + +Your device needs remain unlocked for the duration of the tests. + +The instrumentation tests take a long time to run (depending on the device, about an hour). You can modify the run configuration in Android Studio and add the regex `^(?!.*org\.maplibre\.android\.benchmark).*$` to exclude the benchmark. + +### AWS Device Farm + +The instrumentation tests are running on AWS Device Farm. To see the results and the logs, go to: + +https://us-west-2.console.aws.amazon.com/devicefarm/home?region=us-east-1#/mobile/projects/20687d72-0e46-403e-8f03-0941850665bc/runs + +You can log with the `maplibre` alias, with `maplibre` as username and `maplibre` as password (this is a read-only account). ## Kotlin All new code should be written in [Kotlin](https://kotlinlang.org/). -### Style Checking +## Style Checking To check Kotlin style, we use [ktlint](https://pinterest.github.io/ktlint/). This linter is based on the [official Kotlin coding conventions](https://kotlinlang.org/docs/coding-conventions.html). We intergrate with it using the [kotlinder](https://github.com/jeremymailen/kotlinter-gradle) Gradle plugin. @@ -20,114 +73,4 @@ To format all Kotlin source files, use: ``` $ ./gradlew formatKotlin -``` - -## Getting the source - -Clone the git repository and pull in submodules: - -```bash -git clone git@github.com:maplibre/maplibre-native.git -cd maplibre-native -git submodule update --init --recursive -cd platform/android -``` - -## Installing dependencies - -These dependencies are required for all operating systems and all platform targets. - -- Latest stable [Android Studio](https://developer.android.com/studio/index.html) -- Update the Mapbox Maps SDK for Android with the latest - - Android SDK Build-Tools - - Android Platform-Tools - - Android SDK Tools - - CMake - - NDK - - LLDB -- Modern C++ compiler that supports `-std=c++14`\* - - clang++ 3.5 or later or - - g++-4.9 or later -- [Node.js](https://nodejs.org/) - - make sure [npm](https://www.npmjs.com) is installed as well -- [ccache](https://ccache.samba.org/) (optional) - -**Note**: We partially support C++14 because GCC 4.9 does not fully implement the -final draft of the C++14 standard. More information in [DEVELOPING.md](../../DEVELOPING.md). - -**Note**: On macOS you can install clang with installing the [Apple command line developer tools](https://developer.apple.com/download/). - -## Opening the project - -### macOS - -* Environment - * Android Studio - * node - * `JAVA_HOME="/Applications/Android Studio.app/Contents/jre/Contents/Home"` - * `ANDROID_SDK_ROOT=~/Library/Android/sdk` - * In Android Studio, go to Tools > [SDK Manager](https://developer.android.com/studio/projects/install-ndk#specific-version) to install CMake and the specific NDK version specified in [`dependencies.gradle`](https://github.com/maplibre/maplibre-native/blob/main/platform/android/gradle/dependencies.gradle). - -Open `platform/android` with Android Studio. - -### Linux - -run `make android-configuration` in the root folder of the project and open in Android Studio. - -If you are using Arch Linux, install [ncurses5-compat-libs](https://aur.archlinux.org/packages/ncurses5-compat-libs). - -## Project configuration - -### Setup Checkstyle - -Mapbox uses specific IDE settings related to code and check style. -See [checkstyle guide](https://github.com/mapbox/mapbox-gl-native-android/wiki/Setting-up-Mapbox-checkstyle) for configuration details. - -#### Resolving duplicate file entries -With buck build support, Android Studio can complain about duplicate source files. To remove this warning, open `MapboxGLAndroidSDK.iml` find the list of `excludeFolder` entries and add `` line. - -### Setting API Key - -_The test application (used for development purposes) uses MapTiler vector tiles, which require a MapTiler account and API key._ - -With the first gradle invocation, gradle will take the value of the `MLN_API_KEY` environment variable and save it to `MapboxGLAndroidSDKTestApp/src/main/res/values/developer-config.xml`. If the environment variable wasn't set, you can edit `developer-config.xml` manually and add your api key to the `api_key` resource. - -## Running project - -Run the configuration for the `MapboxGLAndroidSDKTestApp` module and select a device or emulator to deploy on. Based on the selected device, the c++ code will be compiled for the related processor architecture. You can see the project compiling in the `View > Tool Windows > Gradle Console`. - -More information about building and distributing this project in [DISTRIBUTE.md](DISTRIBUTE.md). - -## Additional resources - -### Using the SDK snapshot - -Instead of using the latest stable release of the Maps SDK for Android, you can use a "snapshot" or the beta version if there is one available. Our snapshots are built every time a Github pull request adds code to this repository's `master` branch. If you'd like to use a snapshot build, your Android project's gradle file should have -SNAPSHOT appended to the SDK version number: - -```java -// Mapbox SDK dependency -implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:9.1.0-SNAPSHOT' -``` - -You also need to have the section below in your build.gradle root folder to be able to resolve the SNAPSHOT dependencies: -``` -allprojects { - repositories { - jcenter() - maven { url 'https://oss.jfrog.org/artifactory/oss-snapshot-local/' } - } -} -``` - -### Symbolicating native crashes - -When hitting native crashes you can use ndk-stack to symbolicate crashes. -More information in [this](https://github.com/mapbox/mapbox-gl-native-android/wiki/Getting-line-numbers-from-an-Android-crash-with-ndk-stack) guide. - -## Instrumentation Tests - -The results of the instrumentation tests can be accessed on the AWS Console: - -https://us-west-2.console.aws.amazon.com/devicefarm/home?region=us-east-1#/mobile/projects/20687d72-0e46-403e-8f03-0941850665bc/runs - -You can log with the `maplibre` alias, with `maplibre` as username and `maplibre` as password (this is a read-only account). +``` \ No newline at end of file diff --git a/platform/android/Makefile b/platform/android/Makefile index 81cb2e718..5d180cf40 100644 --- a/platform/android/Makefile +++ b/platform/android/Makefile @@ -234,7 +234,7 @@ run-android-test-app-center: # Uploads the compiled Android SDK to Maven Central Staging .PHONY: run-android-publish run-android-publish: - $(MLN_ANDROID_GRADLE_SINGLE_JOB) --debug -Pmapbox.abis=all :MapboxGLAndroidSDK:publishReleasePublicationToSonatypeRepository closeAndReleaseSonatypeStagingRepository + $(MLN_ANDROID_GRADLE_SINGLE_JOB)-Pmapbox.abis=all :MapboxGLAndroidSDK:publishReleasePublicationToSonatypeRepository closeAndReleaseSonatypeStagingRepository # Dump system graphics information for the test app .PHONY: android-gfxinfo diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/offline/OfflineManager.kt b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/offline/OfflineManager.kt index e234c2781..4bf9ac8ab 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/offline/OfflineManager.kt +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/offline/OfflineManager.kt @@ -21,11 +21,7 @@ import java.nio.channels.FileChannel /** * The offline manager is the main entry point for offline-related functionality. * - * * It'll help you list and create offline regions. - * - * - * @see [Offline Maps Information/](https://docs.mapbox.com/help/troubleshooting/mobile-offline/) */ @UiThread class OfflineManager private constructor(context: Context) { @@ -106,9 +102,6 @@ class OfflineManager private constructor(context: Context) { fun onError(error: String) } - /* - * Constructor - */ init { this.context = context.applicationContext fileSource = FileSource.getInstance(this.context) @@ -126,11 +119,9 @@ class OfflineManager private constructor(context: Context) { /** * Retrieve all regions in the offline database. * - * * The query will be executed asynchronously and the results passed to the given * callback on the main thread. * - * * @param callback the callback to be invoked */ fun listOfflineRegions(callback: ListOfflineRegionsCallback) { @@ -158,26 +149,21 @@ class OfflineManager private constructor(context: Context) { /** * Merge offline regions from a secondary database into the main offline database. * - * * When the merge is completed, or fails, the [MergeOfflineRegionsCallback] will be invoked on the main thread. * The callback reference is **strongly kept** throughout the process, * so it needs to be wrapped in a weak reference or released on the client side if necessary. * - * * The secondary database may need to be upgraded to the latest schema. * This is done in-place and requires write-access to the provided path. * If the app's process doesn't have write-access to the provided path, * the file will be copied to the temporary, internal directory for the duration of the merge. * - * * Only resources and tiles that belong to a region will be copied over. Identical * regions will be flattened into a single new region in the main database. * - * * The operation will be aborted and [MergeOfflineRegionsCallback.onError] with an appropriate message * will be invoked if the merge would result in the offline tile count limit being exceeded. * - * * Merged regions may not be in a completed status if the secondary database * does not contain all the tiles or resources required by the region definition. * @@ -218,12 +204,10 @@ class OfflineManager private constructor(context: Context) { /** * Delete existing database and re-initialize. * - * * When the operation is complete or encounters an error, the given callback will be - * executed on the database thread; it is the responsibility of the SDK bindings + * executed on the database thread; it is the responsibility of the platform bindings * to re-execute a user-provided callback on the main thread. * - * * @param callback the callback to be invoked when the database was reset or when the operation erred. */ fun resetDatabase(callback: FileSourceCallback?) { @@ -248,17 +232,13 @@ class OfflineManager private constructor(context: Context) { /** * Packs the existing database file into a minimal amount of disk space. * - * * This operation has a performance impact as it will vacuum the database, * forcing it to move pages on the filesystem. * - * - * * When the operation is complete or encounters an error, the given callback will be * executed on the database thread; it is the responsibility of the SDK bindings * to re-execute a user-provided callback on the main thread. * - * * @param callback the callback to be invoked when the database was reset or when the operation erred. */ fun packDatabase(callback: FileSourceCallback?) { @@ -283,18 +263,15 @@ class OfflineManager private constructor(context: Context) { /** * Forces re-validation of the ambient cache. * - * - * Forces MapLibre GL Native to revalidate resources stored in the ambient + * Forces MapLibre Native to revalidate resources stored in the ambient * cache with the tile server before using them, making sure they * are the latest version. This is more efficient than cleaning the * cache because if the resource is considered valid after the server * lookup, it will not get downloaded again. * - * * Resources overlapping with offline regions will not be affected * by this call. * - * * @param callback the callback to be invoked when the ambient cache was invalidated or when the operation erred. */ fun invalidateAmbientCache(callback: FileSourceCallback?) { @@ -319,13 +296,10 @@ class OfflineManager private constructor(context: Context) { /** * Erase resources from the ambient cache, freeing storage space. * - * * Erases the ambient cache, freeing resources. * Note that this operation can be potentially slow if packing the database * occurs automatically ([OfflineManager.runPackDatabaseAutomatically]). * - * - * * Resources overlapping with offline regions will not be affected * by this call. * @@ -354,7 +328,6 @@ class OfflineManager private constructor(context: Context) { /** * Sets the maximum size in bytes for the ambient cache. * - * * This call is potentially expensive because it will try * to trim the data in case the database is larger than the * size defined. The size of offline regions are not affected @@ -362,25 +335,17 @@ class OfflineManager private constructor(context: Context) { * to not exceed the maximum size defined, taking into account * the current size for the offline regions. * - * - * * Note that if you use the SDK's offline functionality, your ability to set the ambient cache size will be limited. * Space that offline regions take up detract from the space available for ambient caching, and the ambient cache * size does not block offline downloads. For example: if the maximum cache size is set to 50 MB and 40 MB are * already used by offline regions, the ambient cache size will effectively be 10 MB. * - * - * * Setting the size to 0 will disable the cache if there is no * offline region on the database. * - * <[ - * - * * This method should always be called at the start of an app, before setting the style and loading a map. * Otherwise, the map will instantiate with the default cache size of 50 MB. * - * * @param size the maximum size of the ambient cache * @param callback the callback to be invoked when the the maximum size has been set or when the operation erred. */ @@ -458,23 +423,13 @@ class OfflineManager private constructor(context: Context) { * Creates an offline region in the database by downloading the resources needed to use * the given region offline. * - * - * As of version 8.3.0 of the Maps SDK for Android, offline tile requests are no longer exempt from - * billing on mobile. Developers are subject to separate Vector Tile or Raster Tile API pricing for - * offline use. See [our pricing page](https://www.mapbox.com/pricing/) for more information. - * - * - * * When the initial database queries have completed, the provided callback will be * executed on the main thread. * - * - * * Note that the resulting region will be in an inactive download state; to begin * downloading resources, call `OfflineRegion.setDownloadState(DownloadState.STATE_ACTIVE)`, * optionally registering an `OfflineRegionObserver` beforehand. * - * * @param definition the offline region definition * @param metadata the metadata in bytes * @param callback the callback to be invoked @@ -521,15 +476,13 @@ class OfflineManager private constructor(context: Context) { } /** - * Sets the maximum number of MapLibre-hosted tiles that may be downloaded and stored on the current device. + * Sets the maximum number of tiles that may be downloaded and stored on the current device. * By default, the limit is set to 6,000. * - * * Once this limit is reached, [OfflineRegion.OfflineRegionObserver.mapboxTileCountLimitExceeded] * fires every additional attempt to download additional tiles until already downloaded tiles are removed * by calling [OfflineRegion.delete]. * - * * @param limit the maximum number of tiles allowed to be downloaded */ @Keep @@ -539,7 +492,6 @@ class OfflineManager private constructor(context: Context) { * Sets whether database file packing occurs automatically. * By default, the automatic database file packing is enabled. * - * * If packing is enabled, database file packing occurs automatically * after an offline region is deleted by calling * [OfflineRegion.delete] @@ -548,7 +500,6 @@ class OfflineManager private constructor(context: Context) { * If packing is disabled, disk space will not be freed after * resources are removed unless [OfflineManager.packDatabase] is explicitly called. * - * * @param autopack flag setting the automatic database file packing. */ @@ -593,7 +544,6 @@ class OfflineManager private constructor(context: Context) { * Use this method to pre-warm the cache with resources you know * will be requested. * - * * This call is asynchronous: the data may not be immediately available * for in-progress requests, although subsequent requests should have * access to the cached data. diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/maps/MapGestureDetectorTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/maps/MapGestureDetectorTest.kt index ffeecb76e..2e66ab329 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/maps/MapGestureDetectorTest.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/maps/MapGestureDetectorTest.kt @@ -187,7 +187,7 @@ class MapGestureDetectorTest : BaseTest() { onView(withId(R.id.mapView)).perform(quickScale(-300f, withVelocity = false, duration = 750L)) rule.runOnUiThread { - Assert.assertEquals(3.0, maplibreMap.cameraPosition.zoom, 0.0001) + Assert.assertEquals(3.0, maplibreMap.cameraPosition.zoom, 0.001) } } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml index 1b8d9d849..885a1a2ed 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - MapLibre Android SDK TestApp + MapLibre Native Android TestApp fps30 fps60 This is an empty Fragment diff --git a/platform/android/gradle/publish-root.gradle b/platform/android/gradle/publish-root.gradle index 467ecfa11..6bed2e9db 100644 --- a/platform/android/gradle/publish-root.gradle +++ b/platform/android/gradle/publish-root.gradle @@ -1,21 +1,6 @@ -ext["signing.keyId"] = '' -ext["signing.password"] = '' +ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') +ext["signing.password"] = System.getenv('SIGNING_PASSWORD') ext["signing.secretKeyRingFile"] = "${projectDir}/../signing-key.gpg" -ext["ossrhUsername"] = '' -ext["ossrhPassword"] = '' -ext["sonatypeStagingProfileId"] = '' - -File secretPropsFile = project.rootProject.file('local.properties') -if (secretPropsFile.exists()) { - // Read local.properties file first if it exists - Properties p = new Properties() - new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) } - p.each { name, value -> ext[name] = value } -} else { - // Use system environment variables - ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME') - ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD') - ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID') - ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') - ext["signing.password"] = System.getenv('SIGNING_PASSWORD') -} +ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME') +ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD') +ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID') diff --git a/platform/android/settings.gradle b/platform/android/settings.gradle index c0315fed0..e4eae6064 100644 --- a/platform/android/settings.gradle +++ b/platform/android/settings.gradle @@ -1 +1,4 @@ -include ':MapboxGLAndroidSDK', ':MapboxGLAndroidSDKTestApp', ':MapboxGLAndroidSDKLint' \ No newline at end of file +include ':MapboxGLAndroidSDK', ':MapboxGLAndroidSDKTestApp', ':MapboxGLAndroidSDKLint' + +def renderTestProjectDir = new File(rootDir, '../../render-test/android') +includeBuild(renderTestProjectDir) \ No newline at end of file diff --git a/render-test/android/app/build.gradle b/render-test/android/app/build.gradle index 71f7fc24f..aa3ad60d9 100644 --- a/render-test/android/app/build.gradle +++ b/render-test/android/app/build.gradle @@ -12,7 +12,7 @@ android { abi = project.getProperty('mapbox.abis') } ndk { - if (abi != 'all') { + if (abi != 'all' && abi != 'none') { abiFilters abi.split(' ') } else { abiFilters 'armeabi-v7a', 'x86', 'arm64-v8a', 'x86_64' diff --git a/render-test/android/build.gradle b/render-test/android/build.gradle index 7d6076b86..c763096e6 100644 --- a/render-test/android/build.gradle +++ b/render-test/android/build.gradle @@ -4,7 +4,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:8.0.2' + classpath 'com.android.tools.build:gradle:8.1.4' } } diff --git a/render-test/android/settings.gradle b/render-test/android/settings.gradle index 573abcb32..ae45cfc13 100644 --- a/render-test/android/settings.gradle +++ b/render-test/android/settings.gradle @@ -1,2 +1,3 @@ -include ':app' +rootProject.name = "androidRenderTest" +include ":app" \ No newline at end of file