diff options
author | tobrun <tobrun.van.nuland@gmail.com> | 2019-04-29 15:08:17 +0200 |
---|---|---|
committer | Tobrun <tobrun.van.nuland@gmail.com> | 2019-04-30 13:07:09 +0200 |
commit | 8f90e34094c4d443efe37c235903543caae44420 (patch) | |
tree | 488a372d1e028dc54362d67de283df798efad219 /platform | |
parent | 0fd31267f11bdf015e399d3dd33d063bfd0d9636 (diff) | |
download | qtlocation-mapboxgl-8f90e34094c4d443efe37c235903543caae44420.tar.gz |
[android] - offline test, headless region download
Diffstat (limited to 'platform')
7 files changed, 554 insertions, 2 deletions
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index 01f32b5406..18fbf33cc8 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ android:roundIcon="@drawable/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> + <activity android:name=".activity.FeatureOverviewActivity" android:label="@string/app_name" @@ -953,7 +954,17 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.FeatureOverviewActivity" /> </activity> - + <activity + android:name=".activity.offline.DownloadRegionActivity" + android:description="@string/description_region" + android:label="@string/activity_region"> + <meta-data + android:name="@string/category" + android:value="@string/category_offline" /> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".activity.FeatureOverviewActivity" /> + </activity> <!-- For Instrumentation tests --> <activity android:name=".activity.style.RuntimeStyleTimingTestActivity" diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/DownloadRegionActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/DownloadRegionActivity.kt new file mode 100644 index 0000000000..7f90796f42 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/DownloadRegionActivity.kt @@ -0,0 +1,313 @@ +package com.mapbox.mapboxsdk.testapp.activity.offline + +import android.os.Bundle +import android.os.Handler +import android.support.v7.app.AppCompatActivity +import android.view.View +import android.widget.ArrayAdapter +import android.widget.SeekBar +import android.widget.TextView +import android.widget.Toast +import com.mapbox.mapboxsdk.constants.MapboxConstants +import com.mapbox.mapboxsdk.geometry.LatLng +import com.mapbox.mapboxsdk.geometry.LatLngBounds +import com.mapbox.mapboxsdk.maps.Style +import com.mapbox.mapboxsdk.offline.* +import com.mapbox.mapboxsdk.testapp.R +import kotlinx.android.synthetic.main.activity_region_download.* +import timber.log.Timber +import java.util.* +import java.util.concurrent.TimeUnit + +/** + * Example showcasing how to download an offline region headless, allows to test pausing and resuming the download. + */ +class DownloadRegionActivity : AppCompatActivity(), OfflineRegion.OfflineRegionObserver { + + companion object { + const val STATUS_UPDATE_TIMEOUT_MS = 10_000L + } + + private val handler: Handler = Handler() + private lateinit var offlineManager: OfflineManager + private var offlineRegion: OfflineRegion? = null + private var downloading = false + private var previousCompletedResourceCount: Long = 0 + private var previousUpdateTimestamp: Long = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_region_download) + + offlineManager = OfflineManager.getInstance(this) + offlineManager.setOfflineMapboxTileCountLimit(Long.MAX_VALUE) + initUi() + + deleteOldOfflineRegions { + container.visibility = View.VISIBLE + fab.visibility = View.VISIBLE + } + } + + override fun onDestroy() { + handler.removeCallbacksAndMessages(null) + offlineRegion?.setObserver(null) + super.onDestroy() + } + + private fun createOfflineRegion() { + val latitudeNorth = editTextLatNorth.text.toString().toDouble() + val longitudeEast = editTextLonEast.text.toString().toDouble() + val latitudeSouth = editTextLatSouth.text.toString().toDouble() + val longitudeWest = editTextLonWest.text.toString().toDouble() + val styleUrl = spinnerStyleUrl.selectedItem as String + val maxZoom = seekbarMaxZoom.progress.toFloat() + val minZoom = seekbarMinZoom.progress.toFloat() + + if (!validCoordinates(latitudeNorth, longitudeEast, latitudeSouth, longitudeWest)) { + Toast.makeText(this, "coordinates need to be in valid range", Toast.LENGTH_LONG).show() + return + } + + // create offline definition from data + val definition = OfflineTilePyramidRegionDefinition( + styleUrl, + LatLngBounds.Builder() + .include(LatLng(latitudeNorth, longitudeEast)) + .include(LatLng(latitudeSouth, longitudeWest)) + .build(), + minZoom.toDouble(), + maxZoom.toDouble(), + resources.displayMetrics.density + ) + + logMessage("Creating offline region") + offlineManager.createOfflineRegion(definition, byteArrayOf(), object : OfflineManager.CreateOfflineRegionCallback { + override fun onCreate(region: OfflineRegion) { + logMessage("Region with id ${region.id} created") + offlineRegion = region + startDownload(region) + fab.visibility = View.VISIBLE + } + + override fun onError(error: String) { + logMessage("Failed to create offline region: $error") + } + }) + } + + private fun startDownload(region: OfflineRegion) { + downloading = true + fab.setImageResource(R.drawable.ic_pause_black_24dp) + logMessage("Downloading...") + + region.setObserver(this) + region.setDownloadState(OfflineRegion.STATE_ACTIVE) + + previousUpdateTimestamp = System.currentTimeMillis() + handler.postDelayed(object : Runnable { + override fun run() { + if (System.currentTimeMillis() > previousUpdateTimestamp + STATUS_UPDATE_TIMEOUT_MS) { + logMessage("FAILURE! No progress in ${TimeUnit.MILLISECONDS.toSeconds(STATUS_UPDATE_TIMEOUT_MS)} seconds") + } else { + handler.postDelayed(this, 1000) + } + } + }, 1000) + } + + private fun pauseDownload(region: OfflineRegion) { + downloading = false + fab.setImageResource(R.drawable.ic_play_arrow_black_24dp) + handler.removeCallbacksAndMessages(null) + region.setDownloadState(OfflineRegion.STATE_INACTIVE) + "Paused".let { + logMessage(it) + download_status.text = it + } + } + + override fun onStatusChanged(status: OfflineRegionStatus) { + if (status.isComplete) { + val statusText = "Completed" + logMessage("SUCCESS! $statusText") + download_status.text = statusText + offlineRegion?.setObserver(null) + handler.removeCallbacksAndMessages(null) + } else { + val statusText = "Downloaded ${status.completedResourceCount}/${status.requiredResourceCount}" + Timber.d(statusText) + download_status.text = statusText + + if (status.completedResourceCount > status.requiredResourceCount && + previousCompletedResourceCount <= status.requiredResourceCount) { + logMessage("FAILURE! Completed > required") + } + } + + previousCompletedResourceCount = status.completedResourceCount + previousUpdateTimestamp = System.currentTimeMillis() + } + + override fun onError(error: OfflineRegionError) { + logMessage("Error: $error") + } + + override fun mapboxTileCountLimitExceeded(limit: Long) { + logMessage("Error: tile count limit exceeded") + } + + protected fun logMessage(message: String) { + Timber.d(message) + logView.append(message) + logView.append("\n") + } + + fun deleteOldOfflineRegions(onCompleted: () -> Unit) { + offlineManager.listOfflineRegions(object : OfflineManager.ListOfflineRegionsCallback { + override fun onList(offlineRegions: Array<out OfflineRegion>) { + val count = offlineRegions.size + var remainingCount = count + if (count > 0) { + logMessage("Deleting $count old region...") + offlineRegions.forEach { + it.delete(object : OfflineRegion.OfflineRegionDeleteCallback { + override fun onDelete() { + Timber.d("Deleted region with id ${it.id}") + onProcessed() + } + + override fun onError(error: String) { + Timber.e("Failed to delete region: $error") + onProcessed() + } + + private fun onProcessed() { + remainingCount-- + if (remainingCount == 0) { + logMessage("Done deleting") + onCompleted() + } + } + }) + } + } else { + onCompleted() + } + } + + override fun onError(error: String) { + logMessage("Failed to list offline regions: $error") + onCompleted() + } + }) + } + + // ui + + private val logView: TextView by lazy { + findViewById<TextView>(R.id.log_text) + } + + private fun initUi() { + initEditTexts() + initSeekbars() + initSpinner() + initZoomLevelTextviews() + initSeekbarListeners() + initFab() + } + + private fun initEditTexts() { + editTextLatNorth.setText("62.0") + editTextLonEast.setText("24.0") + editTextLatSouth.setText("60.0") + editTextLonWest.setText("22.5") + } + + private fun initSeekbars() { + val maxZoom = MapboxConstants.MAXIMUM_ZOOM.toInt() + seekbarMinZoom.max = maxZoom + seekbarMinZoom.progress = 1 + seekbarMaxZoom.max = maxZoom + seekbarMaxZoom.progress = 15 + } + + private fun initSpinner() { + val styles = ArrayList<String>() + styles.add(Style.MAPBOX_STREETS) + styles.add(Style.DARK) + styles.add(Style.LIGHT) + styles.add(Style.OUTDOORS) + val spinnerArrayAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, styles) + spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + spinnerStyleUrl.adapter = spinnerArrayAdapter + } + + private fun initZoomLevelTextviews() { + textViewMaxText.text = String.format("Max zoom: %s", seekbarMaxZoom.progress) + textViewMinText.text = String.format("Min zoom: %s", seekbarMinZoom.progress) + } + + private fun initFab() { + fab.setOnClickListener { + container.visibility = View.GONE + if (offlineRegion == null) { + fab.visibility = View.GONE + createOfflineRegion() + } else { + offlineRegion?.let { + if (downloading) { + pauseDownload(it) + } else { + startDownload(it) + } + } + } + } + } + + private fun initSeekbarListeners() { + seekbarMaxZoom.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + textViewMaxText.text = String.format("Max zoom: %s", progress) + } + + override fun onStartTrackingTouch(seekBar: SeekBar) { + + } + + override fun onStopTrackingTouch(seekBar: SeekBar) { + + } + }) + + seekbarMinZoom.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + textViewMinText.text = String.format("Min zoom: %s", progress) + } + + override fun onStartTrackingTouch(seekBar: SeekBar) { + + } + + override fun onStopTrackingTouch(seekBar: SeekBar) { + + } + }) + } + + private fun validCoordinates(latitudeNorth: Double, longitudeEast: Double, latitudeSouth: Double, + longitudeWest: Double): Boolean { + if (latitudeNorth < -90 || latitudeNorth > 90) { + return false + } else if (longitudeEast < -180 || longitudeEast > 180) { + return false + } else if (latitudeSouth < -90 || latitudeSouth > 90) { + return false + } else if (longitudeWest < -180 || longitudeWest > 180) { + return false + } + return true + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_pause_black_24dp.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_pause_black_24dp.xml new file mode 100644 index 0000000000..bb28a6c415 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_pause_black_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="#FF000000" + android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/> +</vector> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_region_download.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_region_download.xml new file mode 100644 index 0000000000..4242da9fbd --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_region_download.xml @@ -0,0 +1,216 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <android.support.v4.widget.NestedScrollView + android:id="@+id/container" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_marginTop="8dp" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="16dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:orientation="horizontal" + android:weightSum="2"> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" + android:text="Latitude north:"/> + + <EditText + android:id="@+id/editTextLatNorth" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:inputType="numberDecimal" + android:maxLines="1"/> + + </LinearLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" + android:text="Longitude east:"/> + + <EditText + android:id="@+id/editTextLonEast" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:inputType="numberDecimal" + android:maxLines="1"/> + + </LinearLayout> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:orientation="horizontal" + android:weightSum="2"> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical" + android:paddingRight="2dp"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" + android:text="Latitude south:"/> + + <EditText + android:id="@+id/editTextLatSouth" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:inputType="numberDecimal" + android:maxLines="1"/> + + </LinearLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical" + android:paddingLeft="2dp"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" + android:text="Longitude west:"/> + + <EditText + android:id="@+id/editTextLonWest" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:inputType="numberDecimal" + android:maxLines="1"/> + + </LinearLayout> + + </LinearLayout> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" + android:text="Style url:"/> + + <Spinner + android:id="@+id/spinnerStyleUrl" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp"/> + + <TextView + android:id="@+id/textViewMinText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp"/> + + <SeekBar + android:id="@+id/seekbarMinZoom" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="12dp"/> + + <TextView + android:id="@+id/textViewMaxText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp"/> + + <SeekBar + android:id="@+id/seekbarMaxZoom" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="12dp"/> + + </LinearLayout> + + </android.support.v4.widget.NestedScrollView> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/log_text" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:textSize="8sp" + tools:text="Log text container"/> + + </FrameLayout> + + <FrameLayout android:layout_width="match_parent" android:layout_height="64dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + android:background="@color/primary" + android:id="@+id/bottom" + app:layout_constraintBottom_toBottomOf="parent"> + <TextView + android:id="@+id/download_status" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:text="Not downloading" + android:textColor="@color/white" + android:textSize="16sp"/> + + </FrameLayout> + + <android.support.design.widget.FloatingActionButton + android:id="@+id/fab" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:visibility="gone" + app:layout_constraintEnd_toEndOf="@id/bottom" + android:background="@color/primary" + app:layout_constraintBottom_toBottomOf="@id/bottom" + app:layout_constraintBottom_toTopOf="@id/bottom" + android:src="@drawable/ic_play_arrow_black_24dp" + android:tint="@android:color/white" + android:layout_marginBottom="64dp" + app:backgroundTint="@color/primary"/> + + +</android.support.constraint.ConstraintLayout>
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index cc2577fb5e..be8dae57a0 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml @@ -18,6 +18,7 @@ <string name="description_map_padding">Map Padding example</string> <string name="description_debug_mode">Debug Mode</string> <string name="description_offline">Offline Map example</string> + <string name="description_region">Download an offline region.</string> <string name="description_update_metadata">Update metadata example</string> <string name="description_offline_region_delete">Delete region example</string> <string name="description_change_resources_cache_path">Change resources cache path example</string> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml index e5f7381918..027198c71b 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml @@ -83,4 +83,5 @@ <string name="activity_nested_viewpager">Nested ViewPager</string> <string name="activity_performance_measurement">Performance Measurement</string> <string name="activity_physical_circle">Physical Unit Circle</string> + <string name="activity_region">Download region</string> </resources>
\ No newline at end of file diff --git a/platform/android/scripts/exclude-activity-gen.json b/platform/android/scripts/exclude-activity-gen.json index a6070edccf..f6156eb0ea 100644 --- a/platform/android/scripts/exclude-activity-gen.json +++ b/platform/android/scripts/exclude-activity-gen.json @@ -50,5 +50,6 @@ "EspressoTestActivity", "FragmentBackStackActivity", "ChildFragmentMapInDialogActivity", - "PerformanceMeasurementActivity" + "PerformanceMeasurementActivity", + "DownloadRegionActivity" ] |