From e99cd6dbaf2a40652487d0bcb2d1e171435f21b4 Mon Sep 17 00:00:00 2001 From: Roman Laitarenko Date: Mon, 6 Oct 2025 17:28:21 +0300 Subject: [PATCH 1/2] Add nautical miles scale bar distance unit --- .../mapbox_maps/mapping/ScaleBarMappings.kt | 4 + .../maps/mapbox_maps/pigeons/Settings.kt | 84 +++++++++++++------ example/integration_test/scale_bar_test.dart | 4 +- .../Classes/Generated/Settings.swift | 72 +++++++++++----- .../Classes/ScalebarController.swift | 16 +++- lib/src/pigeons/settings.dart | 73 +++++++++++----- 6 files changed, 179 insertions(+), 74 deletions(-) diff --git a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/mapping/ScaleBarMappings.kt b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/mapping/ScaleBarMappings.kt index 7be888599..f80d354c9 100644 --- a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/mapping/ScaleBarMappings.kt +++ b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/mapping/ScaleBarMappings.kt @@ -24,6 +24,9 @@ fun ScaleBarSettingsInterface.applyFromFLT(settings: ScaleBarSettings, context: settings.textBorderWidth?.let { this.textBorderWidth = it.toDevicePixels(context) } settings.textSize?.let { this.textSize = it.toFloat() } settings.isMetricUnits?.let { this.isMetricUnits = it } + settings.distanceUnits?.let { + this.distanceUnits = com.mapbox.maps.plugin.DistanceUnits.values()[it.ordinal] + } settings.refreshInterval?.let { this.refreshInterval = it } settings.showTextBorder?.let { this.showTextBorder = it } settings.ratio?.let { this.ratio = it.toFloat() } @@ -47,6 +50,7 @@ fun ScaleBarSettingsInterface.toFLT(context: Context) = ScaleBarSettings( textBorderWidth = textBorderWidth.toLogicalPixels(context), textSize = textSize.toDouble(), isMetricUnits = isMetricUnits, + distanceUnits = DistanceUnits.values()[distanceUnits.ordinal], refreshInterval = refreshInterval, showTextBorder = showTextBorder, ratio = ratio.toDouble(), diff --git a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/Settings.kt b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/Settings.kt index f9a8ac07d..255d5bf49 100644 --- a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/Settings.kt +++ b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/Settings.kt @@ -140,6 +140,25 @@ enum class ModelElevationReference(val raw: Int) { } } +/** + * Supported distance unit types. + * Default value: "metric". + */ +enum class DistanceUnits(val raw: Int) { + /** Metric units using meters and kilometers. The scale bar will automatically choose between meters and kilometers based on the distance being displayed for optimal readability. */ + METRIC(0), + /** Imperial units using feet for short distances and miles for longer distances. The scale bar will display distances in feet for small distances and automatically switch to miles for longer distances. */ + IMPERIAL(1), + /** Nautical units using fathoms for short distances and nautical miles for longer distances. The scale bar will display distances in fathoms for small distances and automatically switch to nautical miles for longer distances. Commonly used for marine and aviation navigation. */ + NAUTICAL(2); + + companion object { + fun ofRaw(raw: Int): DistanceUnits? { + return values().firstOrNull { it.raw == raw } + } + } +} + /** * Gesture configuration allows to control the user touch interaction. * @@ -712,6 +731,11 @@ data class ScaleBarSettings( * Default value: true. */ val isMetricUnits: Boolean? = null, + /** + * Supported distance unit types. + * Default value: "metric". + */ + val distanceUnits: DistanceUnits? = null, /** * Configures minimum refresh interval, in millisecond, default is 15. * Default value: 15. @@ -750,11 +774,12 @@ data class ScaleBarSettings( val textBorderWidth = pigeonVar_list[12] as Double? val textSize = pigeonVar_list[13] as Double? val isMetricUnits = pigeonVar_list[14] as Boolean? - val refreshInterval = pigeonVar_list[15] as Long? - val showTextBorder = pigeonVar_list[16] as Boolean? - val ratio = pigeonVar_list[17] as Double? - val useContinuousRendering = pigeonVar_list[18] as Boolean? - return ScaleBarSettings(enabled, position, marginLeft, marginTop, marginRight, marginBottom, textColor, primaryColor, secondaryColor, borderWidth, height, textBarMargin, textBorderWidth, textSize, isMetricUnits, refreshInterval, showTextBorder, ratio, useContinuousRendering) + val distanceUnits = pigeonVar_list[15] as DistanceUnits? + val refreshInterval = pigeonVar_list[16] as Long? + val showTextBorder = pigeonVar_list[17] as Boolean? + val ratio = pigeonVar_list[18] as Double? + val useContinuousRendering = pigeonVar_list[19] as Boolean? + return ScaleBarSettings(enabled, position, marginLeft, marginTop, marginRight, marginBottom, textColor, primaryColor, secondaryColor, borderWidth, height, textBarMargin, textBorderWidth, textSize, isMetricUnits, distanceUnits, refreshInterval, showTextBorder, ratio, useContinuousRendering) } } fun toList(): List { @@ -774,6 +799,7 @@ data class ScaleBarSettings( textBorderWidth, textSize, isMetricUnits, + distanceUnits, refreshInterval, showTextBorder, ratio, @@ -802,6 +828,7 @@ data class ScaleBarSettings( textBorderWidth == other.textBorderWidth && textSize == other.textSize && isMetricUnits == other.isMetricUnits && + distanceUnits == other.distanceUnits && refreshInterval == other.refreshInterval && showTextBorder == other.showTextBorder && ratio == other.ratio && @@ -1130,51 +1157,56 @@ private open class SettingsPigeonCodec : StandardMessageCodec() { } } 134.toByte() -> { + return (readValue(buffer) as Long?)?.let { + DistanceUnits.ofRaw(it.toInt()) + } + } + 135.toByte() -> { return (readValue(buffer) as? List)?.let { ScreenCoordinate.fromList(it) } } - 135.toByte() -> { + 136.toByte() -> { return (readValue(buffer) as? List)?.let { GesturesSettings.fromList(it) } } - 136.toByte() -> { + 137.toByte() -> { return (readValue(buffer) as? List)?.let { LocationPuck2D.fromList(it) } } - 137.toByte() -> { + 138.toByte() -> { return (readValue(buffer) as? List)?.let { LocationPuck3D.fromList(it) } } - 138.toByte() -> { + 139.toByte() -> { return (readValue(buffer) as? List)?.let { LocationPuck.fromList(it) } } - 139.toByte() -> { + 140.toByte() -> { return (readValue(buffer) as? List)?.let { LocationComponentSettings.fromList(it) } } - 140.toByte() -> { + 141.toByte() -> { return (readValue(buffer) as? List)?.let { ScaleBarSettings.fromList(it) } } - 141.toByte() -> { + 142.toByte() -> { return (readValue(buffer) as? List)?.let { CompassSettings.fromList(it) } } - 142.toByte() -> { + 143.toByte() -> { return (readValue(buffer) as? List)?.let { AttributionSettings.fromList(it) } } - 143.toByte() -> { + 144.toByte() -> { return (readValue(buffer) as? List)?.let { LogoSettings.fromList(it) } @@ -1204,44 +1236,48 @@ private open class SettingsPigeonCodec : StandardMessageCodec() { stream.write(133) writeValue(stream, value.raw) } - is ScreenCoordinate -> { + is DistanceUnits -> { stream.write(134) + writeValue(stream, value.raw) + } + is ScreenCoordinate -> { + stream.write(135) writeValue(stream, value.toList()) } is GesturesSettings -> { - stream.write(135) + stream.write(136) writeValue(stream, value.toList()) } is LocationPuck2D -> { - stream.write(136) + stream.write(137) writeValue(stream, value.toList()) } is LocationPuck3D -> { - stream.write(137) + stream.write(138) writeValue(stream, value.toList()) } is LocationPuck -> { - stream.write(138) + stream.write(139) writeValue(stream, value.toList()) } is LocationComponentSettings -> { - stream.write(139) + stream.write(140) writeValue(stream, value.toList()) } is ScaleBarSettings -> { - stream.write(140) + stream.write(141) writeValue(stream, value.toList()) } is CompassSettings -> { - stream.write(141) + stream.write(142) writeValue(stream, value.toList()) } is AttributionSettings -> { - stream.write(142) + stream.write(143) writeValue(stream, value.toList()) } is LogoSettings -> { - stream.write(143) + stream.write(144) writeValue(stream, value.toList()) } else -> super.writeValue(stream, value) diff --git a/example/integration_test/scale_bar_test.dart b/example/integration_test/scale_bar_test.dart index bfea2344e..bef168f67 100644 --- a/example/integration_test/scale_bar_test.dart +++ b/example/integration_test/scale_bar_test.dart @@ -29,7 +29,7 @@ void main() { textBarMargin: 1, textBorderWidth: 2, textSize: 10, - isMetricUnits: true, + distanceUnits: DistanceUnits.NAUTICAL, refreshInterval: 10, showTextBorder: true, ratio: 1.5, @@ -38,7 +38,7 @@ void main() { await scaleBar.updateSettings(settings); var updatedSettings = await scaleBar.getSettings(); expect(updatedSettings.position, OrnamentPosition.BOTTOM_RIGHT); - expect(updatedSettings.isMetricUnits, true); + expect(updatedSettings.distanceUnits, DistanceUnits.NAUTICAL); if (Platform.isIOS) { expect(updatedSettings.marginRight, 3); expect(updatedSettings.marginBottom, 4); diff --git a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Generated/Settings.swift b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Generated/Settings.swift index f72492f5e..b6070a655 100644 --- a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Generated/Settings.swift +++ b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/Generated/Settings.swift @@ -109,6 +109,17 @@ enum ModelElevationReference: Int { case gROUND = 1 } +/// Supported distance unit types. +/// Default value: "metric". +enum DistanceUnits: Int { + /// Metric units using meters and kilometers. The scale bar will automatically choose between meters and kilometers based on the distance being displayed for optimal readability. + case mETRIC = 0 + /// Imperial units using feet for short distances and miles for longer distances. The scale bar will display distances in feet for small distances and automatically switch to miles for longer distances. + case iMPERIAL = 1 + /// Nautical units using fathoms for short distances and nautical miles for longer distances. The scale bar will display distances in fathoms for small distances and automatically switch to nautical miles for longer distances. Commonly used for marine and aviation navigation. + case nAUTICAL = 2 +} + /// Gesture configuration allows to control the user touch interaction. /// /// Generated class from Pigeon that represents data sent in messages. @@ -529,6 +540,9 @@ struct ScaleBarSettings { /// Whether the scale bar is using metric unit. True if the scale bar is using metric system, false if the scale bar is using imperial units. /// Default value: true. var isMetricUnits: Bool? = nil + /// Supported distance unit types. + /// Default value: "metric". + var distanceUnits: DistanceUnits? = nil /// Configures minimum refresh interval, in millisecond, default is 15. /// Default value: 15. var refreshInterval: Int64? = nil @@ -560,10 +574,11 @@ struct ScaleBarSettings { let textBorderWidth: Double? = nilOrValue(pigeonVar_list[12]) let textSize: Double? = nilOrValue(pigeonVar_list[13]) let isMetricUnits: Bool? = nilOrValue(pigeonVar_list[14]) - let refreshInterval: Int64? = nilOrValue(pigeonVar_list[15]) - let showTextBorder: Bool? = nilOrValue(pigeonVar_list[16]) - let ratio: Double? = nilOrValue(pigeonVar_list[17]) - let useContinuousRendering: Bool? = nilOrValue(pigeonVar_list[18]) + let distanceUnits: DistanceUnits? = nilOrValue(pigeonVar_list[15]) + let refreshInterval: Int64? = nilOrValue(pigeonVar_list[16]) + let showTextBorder: Bool? = nilOrValue(pigeonVar_list[17]) + let ratio: Double? = nilOrValue(pigeonVar_list[18]) + let useContinuousRendering: Bool? = nilOrValue(pigeonVar_list[19]) return ScaleBarSettings( enabled: enabled, @@ -581,6 +596,7 @@ struct ScaleBarSettings { textBorderWidth: textBorderWidth, textSize: textSize, isMetricUnits: isMetricUnits, + distanceUnits: distanceUnits, refreshInterval: refreshInterval, showTextBorder: showTextBorder, ratio: ratio, @@ -604,6 +620,7 @@ struct ScaleBarSettings { textBorderWidth, textSize, isMetricUnits, + distanceUnits, refreshInterval, showTextBorder, ratio, @@ -857,24 +874,30 @@ private class SettingsPigeonCodecReader: FlutterStandardReader { } return nil case 134: - return ScreenCoordinate.fromList(self.readValue() as! [Any?]) + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return DistanceUnits(rawValue: enumResultAsInt) + } + return nil case 135: - return GesturesSettings.fromList(self.readValue() as! [Any?]) + return ScreenCoordinate.fromList(self.readValue() as! [Any?]) case 136: - return LocationPuck2D.fromList(self.readValue() as! [Any?]) + return GesturesSettings.fromList(self.readValue() as! [Any?]) case 137: - return LocationPuck3D.fromList(self.readValue() as! [Any?]) + return LocationPuck2D.fromList(self.readValue() as! [Any?]) case 138: - return LocationPuck.fromList(self.readValue() as! [Any?]) + return LocationPuck3D.fromList(self.readValue() as! [Any?]) case 139: - return LocationComponentSettings.fromList(self.readValue() as! [Any?]) + return LocationPuck.fromList(self.readValue() as! [Any?]) case 140: - return ScaleBarSettings.fromList(self.readValue() as! [Any?]) + return LocationComponentSettings.fromList(self.readValue() as! [Any?]) case 141: - return CompassSettings.fromList(self.readValue() as! [Any?]) + return ScaleBarSettings.fromList(self.readValue() as! [Any?]) case 142: - return AttributionSettings.fromList(self.readValue() as! [Any?]) + return CompassSettings.fromList(self.readValue() as! [Any?]) case 143: + return AttributionSettings.fromList(self.readValue() as! [Any?]) + case 144: return LogoSettings.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) @@ -899,35 +922,38 @@ private class SettingsPigeonCodecWriter: FlutterStandardWriter { } else if let value = value as? ModelElevationReference { super.writeByte(133) super.writeValue(value.rawValue) - } else if let value = value as? ScreenCoordinate { + } else if let value = value as? DistanceUnits { super.writeByte(134) + super.writeValue(value.rawValue) + } else if let value = value as? ScreenCoordinate { + super.writeByte(135) super.writeValue(value.toList()) } else if let value = value as? GesturesSettings { - super.writeByte(135) + super.writeByte(136) super.writeValue(value.toList()) } else if let value = value as? LocationPuck2D { - super.writeByte(136) + super.writeByte(137) super.writeValue(value.toList()) } else if let value = value as? LocationPuck3D { - super.writeByte(137) + super.writeByte(138) super.writeValue(value.toList()) } else if let value = value as? LocationPuck { - super.writeByte(138) + super.writeByte(139) super.writeValue(value.toList()) } else if let value = value as? LocationComponentSettings { - super.writeByte(139) + super.writeByte(140) super.writeValue(value.toList()) } else if let value = value as? ScaleBarSettings { - super.writeByte(140) + super.writeByte(141) super.writeValue(value.toList()) } else if let value = value as? CompassSettings { - super.writeByte(141) + super.writeByte(142) super.writeValue(value.toList()) } else if let value = value as? AttributionSettings { - super.writeByte(142) + super.writeByte(143) super.writeValue(value.toList()) } else if let value = value as? LogoSettings { - super.writeByte(143) + super.writeByte(144) super.writeValue(value.toList()) } else { super.writeValue(value) diff --git a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/ScalebarController.swift b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/ScalebarController.swift index 1d3d48d22..4c64bc2f5 100644 --- a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/ScalebarController.swift +++ b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/ScalebarController.swift @@ -25,14 +25,25 @@ final class ScaleBarController: ScaleBarSettingsInterface { if let visible = settings.enabled { scaleBar.visibility = visible ? .adaptive : .hidden } - + if let units = settings.distanceUnits { + scaleBar.units = switch units { + case .mETRIC: .metric + case .iMPERIAL: .imperial + case .nAUTICAL: .nautical + } + } ornaments.options.scaleBar = scaleBar } func getSettings() throws -> ScaleBarSettings { let options = ornaments.options.scaleBar let position = getFLT_SETTINGSOrnamentPosition(position: options.position) - + let units: DistanceUnits? = switch ornaments.options.scaleBar.units { + case .metric: .mETRIC + case .imperial: .iMPERIAL + case .nautical: .nAUTICAL + default: nil + } return ScaleBarSettings( enabled: ornaments.options.scaleBar.visibility != OrnamentVisibility.hidden, position: position, @@ -49,6 +60,7 @@ final class ScaleBarController: ScaleBarSettingsInterface { textBorderWidth: nil, textSize: nil, isMetricUnits: ornaments.options.scaleBar.useMetricUnits, + distanceUnits: units, refreshInterval: nil, showTextBorder: nil, ratio: nil, diff --git a/lib/src/pigeons/settings.dart b/lib/src/pigeons/settings.dart index 702dfce40..da080df3b 100644 --- a/lib/src/pigeons/settings.dart +++ b/lib/src/pigeons/settings.dart @@ -54,6 +54,19 @@ enum ModelElevationReference { GROUND, } +/// Supported distance unit types. +/// Default value: "metric". +enum DistanceUnits { + /// Metric units using meters and kilometers. The scale bar will automatically choose between meters and kilometers based on the distance being displayed for optimal readability. + METRIC, + + /// Imperial units using feet for short distances and miles for longer distances. The scale bar will display distances in feet for small distances and automatically switch to miles for longer distances. + IMPERIAL, + + /// Nautical units using fathoms for short distances and nautical miles for longer distances. The scale bar will display distances in fathoms for small distances and automatically switch to nautical miles for longer distances. Commonly used for marine and aviation navigation. + NAUTICAL, +} + /// Gesture configuration allows to control the user touch interaction. class GesturesSettings { GesturesSettings({ @@ -641,6 +654,7 @@ class ScaleBarSettings { this.textBorderWidth, this.textSize, this.isMetricUnits, + this.distanceUnits, this.refreshInterval, this.showTextBorder, this.ratio, @@ -707,6 +721,10 @@ class ScaleBarSettings { /// Default value: true. bool? isMetricUnits; + /// Supported distance unit types. + /// Default value: "metric". + DistanceUnits? distanceUnits; + /// Configures minimum refresh interval, in millisecond, default is 15. /// Default value: 15. int? refreshInterval; @@ -740,6 +758,7 @@ class ScaleBarSettings { textBorderWidth, textSize, isMetricUnits, + distanceUnits, refreshInterval, showTextBorder, ratio, @@ -769,10 +788,11 @@ class ScaleBarSettings { textBorderWidth: result[12] as double?, textSize: result[13] as double?, isMetricUnits: result[14] as bool?, - refreshInterval: result[15] as int?, - showTextBorder: result[16] as bool?, - ratio: result[17] as double?, - useContinuousRendering: result[18] as bool?, + distanceUnits: result[15] as DistanceUnits?, + refreshInterval: result[16] as int?, + showTextBorder: result[17] as bool?, + ratio: result[18] as double?, + useContinuousRendering: result[19] as bool?, ); } @@ -800,6 +820,7 @@ class ScaleBarSettings { textBorderWidth == other.textBorderWidth && textSize == other.textSize && isMetricUnits == other.isMetricUnits && + distanceUnits == other.distanceUnits && refreshInterval == other.refreshInterval && showTextBorder == other.showTextBorder && ratio == other.ratio && @@ -1150,35 +1171,38 @@ class Settings_PigeonCodec extends StandardMessageCodec { } else if (value is ModelElevationReference) { buffer.putUint8(133); writeValue(buffer, value.index); - } else if (value is ScreenCoordinate) { + } else if (value is DistanceUnits) { buffer.putUint8(134); + writeValue(buffer, value.index); + } else if (value is ScreenCoordinate) { + buffer.putUint8(135); writeValue(buffer, value.encode()); } else if (value is GesturesSettings) { - buffer.putUint8(135); + buffer.putUint8(136); writeValue(buffer, value.encode()); } else if (value is LocationPuck2D) { - buffer.putUint8(136); + buffer.putUint8(137); writeValue(buffer, value.encode()); } else if (value is LocationPuck3D) { - buffer.putUint8(137); + buffer.putUint8(138); writeValue(buffer, value.encode()); } else if (value is LocationPuck) { - buffer.putUint8(138); + buffer.putUint8(139); writeValue(buffer, value.encode()); } else if (value is LocationComponentSettings) { - buffer.putUint8(139); + buffer.putUint8(140); writeValue(buffer, value.encode()); } else if (value is ScaleBarSettings) { - buffer.putUint8(140); + buffer.putUint8(141); writeValue(buffer, value.encode()); } else if (value is CompassSettings) { - buffer.putUint8(141); + buffer.putUint8(142); writeValue(buffer, value.encode()); } else if (value is AttributionSettings) { - buffer.putUint8(142); + buffer.putUint8(143); writeValue(buffer, value.encode()); } else if (value is LogoSettings) { - buffer.putUint8(143); + buffer.putUint8(144); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); @@ -1204,24 +1228,27 @@ class Settings_PigeonCodec extends StandardMessageCodec { final int? value = readValue(buffer) as int?; return value == null ? null : ModelElevationReference.values[value]; case 134: - return ScreenCoordinate.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null ? null : DistanceUnits.values[value]; case 135: - return GesturesSettings.decode(readValue(buffer)!); + return ScreenCoordinate.decode(readValue(buffer)!); case 136: - return LocationPuck2D.decode(readValue(buffer)!); + return GesturesSettings.decode(readValue(buffer)!); case 137: - return LocationPuck3D.decode(readValue(buffer)!); + return LocationPuck2D.decode(readValue(buffer)!); case 138: - return LocationPuck.decode(readValue(buffer)!); + return LocationPuck3D.decode(readValue(buffer)!); case 139: - return LocationComponentSettings.decode(readValue(buffer)!); + return LocationPuck.decode(readValue(buffer)!); case 140: - return ScaleBarSettings.decode(readValue(buffer)!); + return LocationComponentSettings.decode(readValue(buffer)!); case 141: - return CompassSettings.decode(readValue(buffer)!); + return ScaleBarSettings.decode(readValue(buffer)!); case 142: - return AttributionSettings.decode(readValue(buffer)!); + return CompassSettings.decode(readValue(buffer)!); case 143: + return AttributionSettings.decode(readValue(buffer)!); + case 144: return LogoSettings.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); From 5e978b902cfd2f5b8019cd6b156be7073d54b7d3 Mon Sep 17 00:00:00 2001 From: Roman Laitarenko Date: Mon, 6 Oct 2025 17:28:55 +0300 Subject: [PATCH 2/2] add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8da3bc9d4..9d55202ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### main + +* Add nautical miles scale bar distance unit. + ### 2.12.0-beta.1 * Use Maps SDK Android dependency with NDK 27 support and [support for 16 KB page sizes](https://developer.android.com/guide/practices/page-sizes).