expo-gaode-map-navigation 2.0.12-next.0 → 2.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/README.md +296 -7
  2. package/android/build.gradle +12 -4
  3. package/android/src/main/AndroidManifest.xml +10 -1
  4. package/android/src/main/cpp/cluster_jni.cpp +56 -0
  5. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapModule.kt +49 -8
  6. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapOfflineModule.kt +83 -15
  7. package/android/src/main/java/expo/modules/gaodemap/map/ExpoGaodeMapView.kt +13 -3
  8. package/android/src/main/java/expo/modules/gaodemap/map/managers/UIManager.kt +36 -39
  9. package/android/src/main/java/expo/modules/gaodemap/map/modules/SDKInitializer.kt +23 -17
  10. package/android/src/main/java/expo/modules/gaodemap/map/overlays/ClusterView.kt +5 -2
  11. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapView.kt +122 -10
  12. package/android/src/main/java/expo/modules/gaodemap/map/overlays/HeatMapViewModule.kt +2 -2
  13. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerView.kt +37 -25
  14. package/android/src/main/java/expo/modules/gaodemap/map/overlays/MarkerViewModule.kt +6 -6
  15. package/android/src/main/java/expo/modules/gaodemap/map/search/ExpoGaodeMapSearchModule.kt +751 -0
  16. package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +103 -0
  17. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviView.kt +1408 -394
  18. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNaviViewModule.kt +121 -1
  19. package/android/src/main/java/expo/modules/gaodemap/navigation/ExpoGaodeMapNavigationModule.kt +14 -28
  20. package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/IndependentRouteListener.kt +28 -3
  21. package/android/src/main/java/expo/modules/gaodemap/navigation/listeners/RouteCalculateListener.kt +2 -2
  22. package/android/src/main/java/expo/modules/gaodemap/navigation/managers/IndependentRouteManager.kt +114 -15
  23. package/android/src/main/java/expo/modules/gaodemap/navigation/routes/drive/DriveTruckRouteCalculator.kt +24 -35
  24. package/android/src/main/java/expo/modules/gaodemap/navigation/services/IndependentRouteService.kt +50 -36
  25. package/android/src/main/java/expo/modules/gaodemap/navigation/services/NavigationForegroundService.kt +661 -0
  26. package/android/src/main/java/expo/modules/gaodemap/navigation/utils/Converters.kt +21 -12
  27. package/android/src/main/res/drawable/ic_nav_notification_small.xml +10 -0
  28. package/android/src/main/res/drawable/landback_0.png +0 -0
  29. package/android/src/main/res/drawable/landback_1.png +0 -0
  30. package/android/src/main/res/drawable/landback_2.png +0 -0
  31. package/android/src/main/res/drawable/landback_3.png +0 -0
  32. package/android/src/main/res/drawable/landback_4.png +0 -0
  33. package/android/src/main/res/drawable/landback_5.png +0 -0
  34. package/android/src/main/res/drawable/landback_6.png +0 -0
  35. package/android/src/main/res/drawable/landback_7.png +0 -0
  36. package/android/src/main/res/drawable/landback_8.png +0 -0
  37. package/android/src/main/res/drawable/landback_9.png +0 -0
  38. package/android/src/main/res/drawable/landback_a.png +0 -0
  39. package/android/src/main/res/drawable/landback_b.png +0 -0
  40. package/android/src/main/res/drawable/landback_c.png +0 -0
  41. package/android/src/main/res/drawable/landback_d.png +0 -0
  42. package/android/src/main/res/drawable/landback_e.png +0 -0
  43. package/android/src/main/res/drawable/landback_f.png +0 -0
  44. package/android/src/main/res/drawable/landback_g.png +0 -0
  45. package/android/src/main/res/drawable/landback_h.png +0 -0
  46. package/android/src/main/res/drawable/landback_i.png +0 -0
  47. package/android/src/main/res/drawable/landback_j.png +0 -0
  48. package/android/src/main/res/drawable/landback_k.png +0 -0
  49. package/android/src/main/res/drawable/landback_l.png +0 -0
  50. package/android/src/main/res/drawable/landfront_0.png +0 -0
  51. package/android/src/main/res/drawable/landfront_00.png +0 -0
  52. package/android/src/main/res/drawable/landfront_1.png +0 -0
  53. package/android/src/main/res/drawable/landfront_11.png +0 -0
  54. package/android/src/main/res/drawable/landfront_20.png +0 -0
  55. package/android/src/main/res/drawable/landfront_21.png +0 -0
  56. package/android/src/main/res/drawable/landfront_22.png +0 -0
  57. package/android/src/main/res/drawable/landfront_3.png +0 -0
  58. package/android/src/main/res/drawable/landfront_33.png +0 -0
  59. package/android/src/main/res/drawable/landfront_40.png +0 -0
  60. package/android/src/main/res/drawable/landfront_43.png +0 -0
  61. package/android/src/main/res/drawable/landfront_44.png +0 -0
  62. package/android/src/main/res/drawable/landfront_5.png +0 -0
  63. package/android/src/main/res/drawable/landfront_55.png +0 -0
  64. package/android/src/main/res/drawable/landfront_61.png +0 -0
  65. package/android/src/main/res/drawable/landfront_63.png +0 -0
  66. package/android/src/main/res/drawable/landfront_66.png +0 -0
  67. package/android/src/main/res/drawable/landfront_70.png +0 -0
  68. package/android/src/main/res/drawable/landfront_71.png +0 -0
  69. package/android/src/main/res/drawable/landfront_73.png +0 -0
  70. package/android/src/main/res/drawable/landfront_77.png +0 -0
  71. package/android/src/main/res/drawable/landfront_8.png +0 -0
  72. package/android/src/main/res/drawable/landfront_88.png +0 -0
  73. package/android/src/main/res/drawable/landfront_90.png +0 -0
  74. package/android/src/main/res/drawable/landfront_95.png +0 -0
  75. package/android/src/main/res/drawable/landfront_99.png +0 -0
  76. package/android/src/main/res/drawable/landfront_a0.png +0 -0
  77. package/android/src/main/res/drawable/landfront_a8.png +0 -0
  78. package/android/src/main/res/drawable/landfront_aa.png +0 -0
  79. package/android/src/main/res/drawable/landfront_b1.png +0 -0
  80. package/android/src/main/res/drawable/landfront_b5.png +0 -0
  81. package/android/src/main/res/drawable/landfront_bb.png +0 -0
  82. package/android/src/main/res/drawable/landfront_c3.png +0 -0
  83. package/android/src/main/res/drawable/landfront_c8.png +0 -0
  84. package/android/src/main/res/drawable/landfront_cc.png +0 -0
  85. package/android/src/main/res/drawable/landfront_d.png +0 -0
  86. package/android/src/main/res/drawable/landfront_dd.png +0 -0
  87. package/android/src/main/res/drawable/landfront_e1.png +0 -0
  88. package/android/src/main/res/drawable/landfront_e5.png +0 -0
  89. package/android/src/main/res/drawable/landfront_ee.png +0 -0
  90. package/android/src/main/res/drawable/landfront_f0.png +0 -0
  91. package/android/src/main/res/drawable/landfront_f1.png +0 -0
  92. package/android/src/main/res/drawable/landfront_f5.png +0 -0
  93. package/android/src/main/res/drawable/landfront_ff.png +0 -0
  94. package/android/src/main/res/drawable/landfront_g3.png +0 -0
  95. package/android/src/main/res/drawable/landfront_g5.png +0 -0
  96. package/android/src/main/res/drawable/landfront_gg.png +0 -0
  97. package/android/src/main/res/drawable/landfront_h1.png +0 -0
  98. package/android/src/main/res/drawable/landfront_h3.png +0 -0
  99. package/android/src/main/res/drawable/landfront_h5.png +0 -0
  100. package/android/src/main/res/drawable/landfront_hh.png +0 -0
  101. package/android/src/main/res/drawable/landfront_i0.png +0 -0
  102. package/android/src/main/res/drawable/landfront_i3.png +0 -0
  103. package/android/src/main/res/drawable/landfront_i5.png +0 -0
  104. package/android/src/main/res/drawable/landfront_ii.png +0 -0
  105. package/android/src/main/res/drawable/landfront_j1.png +0 -0
  106. package/android/src/main/res/drawable/landfront_j8.png +0 -0
  107. package/android/src/main/res/drawable/landfront_jj.png +0 -0
  108. package/android/src/main/res/drawable/landfront_kk.png +0 -0
  109. package/android/src/main/res/drawable/landfront_ll.png +0 -0
  110. package/android/src/main/res/drawable/nav_notification_brand_icon.xml +16 -0
  111. package/android/src/main/res/drawable/navi_arrow_leftline.png +0 -0
  112. package/android/src/main/res/drawable/navi_lane_shape_bg_center.xml +5 -0
  113. package/android/src/main/res/drawable/navi_lane_shape_bg_left.xml +8 -0
  114. package/android/src/main/res/drawable/navi_lane_shape_bg_over.xml +6 -0
  115. package/android/src/main/res/drawable/navi_lane_shape_bg_right.xml +8 -0
  116. package/android/src/main/res/drawable-nodpi/nav_tracker_car.png +0 -0
  117. package/build/ExpoGaodeMapNaviView.d.ts +16 -0
  118. package/build/ExpoGaodeMapNaviView.d.ts.map +1 -1
  119. package/build/ExpoGaodeMapNaviView.js +74 -1
  120. package/build/ExpoGaodeMapNaviView.js.map +1 -1
  121. package/build/index.d.ts +56 -8
  122. package/build/index.d.ts.map +1 -1
  123. package/build/index.js +452 -10
  124. package/build/index.js.map +1 -1
  125. package/build/map/ExpoGaodeMapModule.d.ts +15 -13
  126. package/build/map/ExpoGaodeMapModule.d.ts.map +1 -1
  127. package/build/map/ExpoGaodeMapModule.js +31 -39
  128. package/build/map/ExpoGaodeMapModule.js.map +1 -1
  129. package/build/map/ExpoGaodeMapOfflineModule.d.ts +5 -0
  130. package/build/map/ExpoGaodeMapOfflineModule.d.ts.map +1 -1
  131. package/build/map/ExpoGaodeMapOfflineModule.js.map +1 -1
  132. package/build/map/ExpoGaodeMapView.d.ts +3 -4
  133. package/build/map/ExpoGaodeMapView.d.ts.map +1 -1
  134. package/build/map/ExpoGaodeMapView.js +28 -25
  135. package/build/map/ExpoGaodeMapView.js.map +1 -1
  136. package/build/map/components/overlays/Circle.d.ts.map +1 -1
  137. package/build/map/components/overlays/Circle.js +1 -30
  138. package/build/map/components/overlays/Circle.js.map +1 -1
  139. package/build/map/components/overlays/Cluster.d.ts.map +1 -1
  140. package/build/map/components/overlays/Cluster.js +1 -42
  141. package/build/map/components/overlays/Cluster.js.map +1 -1
  142. package/build/map/components/overlays/HeatMap.d.ts.map +1 -1
  143. package/build/map/components/overlays/HeatMap.js +21 -21
  144. package/build/map/components/overlays/HeatMap.js.map +1 -1
  145. package/build/map/components/overlays/Marker.d.ts.map +1 -1
  146. package/build/map/components/overlays/Marker.js +76 -80
  147. package/build/map/components/overlays/Marker.js.map +1 -1
  148. package/build/map/components/overlays/Polygon.d.ts.map +1 -1
  149. package/build/map/components/overlays/Polygon.js +1 -25
  150. package/build/map/components/overlays/Polygon.js.map +1 -1
  151. package/build/map/components/overlays/Polyline.d.ts.map +1 -1
  152. package/build/map/components/overlays/Polyline.js +1 -31
  153. package/build/map/components/overlays/Polyline.js.map +1 -1
  154. package/build/map/index.d.ts +9 -2
  155. package/build/map/index.d.ts.map +1 -1
  156. package/build/map/index.js +9 -2
  157. package/build/map/index.js.map +1 -1
  158. package/build/map/search/ExpoGaodeMapSearch.types.d.ts +340 -0
  159. package/build/map/search/ExpoGaodeMapSearch.types.d.ts.map +1 -0
  160. package/build/map/search/ExpoGaodeMapSearch.types.js +19 -0
  161. package/build/map/search/ExpoGaodeMapSearch.types.js.map +1 -0
  162. package/build/map/search/ExpoGaodeMapSearchModule.d.ts +74 -0
  163. package/build/map/search/ExpoGaodeMapSearchModule.d.ts.map +1 -0
  164. package/build/map/search/ExpoGaodeMapSearchModule.js +47 -0
  165. package/build/map/search/ExpoGaodeMapSearchModule.js.map +1 -0
  166. package/build/map/search/index.d.ts +156 -0
  167. package/build/map/search/index.d.ts.map +1 -0
  168. package/build/map/search/index.js +171 -0
  169. package/build/map/search/index.js.map +1 -0
  170. package/build/map/types/index.d.ts +2 -2
  171. package/build/map/types/index.d.ts.map +1 -1
  172. package/build/map/types/index.js.map +1 -1
  173. package/build/map/types/map-view.types.d.ts +4 -2
  174. package/build/map/types/map-view.types.d.ts.map +1 -1
  175. package/build/map/types/map-view.types.js.map +1 -1
  176. package/build/map/types/native-module.types.d.ts +11 -12
  177. package/build/map/types/native-module.types.d.ts.map +1 -1
  178. package/build/map/types/native-module.types.js.map +1 -1
  179. package/build/map/types/overlays.types.d.ts +9 -14
  180. package/build/map/types/overlays.types.d.ts.map +1 -1
  181. package/build/map/types/overlays.types.js.map +1 -1
  182. package/build/map/types/route-playback.types.d.ts +16 -0
  183. package/build/map/types/route-playback.types.d.ts.map +1 -1
  184. package/build/map/types/route-playback.types.js.map +1 -1
  185. package/build/map/utils/ErrorHandler.js +11 -11
  186. package/build/map/utils/ErrorHandler.js.map +1 -1
  187. package/build/map/utils/OfflineMapManager.d.ts +4 -0
  188. package/build/map/utils/OfflineMapManager.d.ts.map +1 -1
  189. package/build/map/utils/OfflineMapManager.js +6 -0
  190. package/build/map/utils/OfflineMapManager.js.map +1 -1
  191. package/build/types/coordinates.types.d.ts +3 -0
  192. package/build/types/coordinates.types.d.ts.map +1 -1
  193. package/build/types/coordinates.types.js.map +1 -1
  194. package/build/types/independent.types.d.ts +111 -12
  195. package/build/types/independent.types.d.ts.map +1 -1
  196. package/build/types/independent.types.js.map +1 -1
  197. package/build/types/native-module.types.d.ts +1 -1
  198. package/build/types/native-module.types.js.map +1 -1
  199. package/build/types/naviview.types.d.ts +304 -14
  200. package/build/types/naviview.types.d.ts.map +1 -1
  201. package/build/types/naviview.types.js.map +1 -1
  202. package/build/types/route.types.d.ts +12 -2
  203. package/build/types/route.types.d.ts.map +1 -1
  204. package/build/types/route.types.js.map +1 -1
  205. package/expo-module.config.json +4 -2
  206. package/ios/ExpoGaodeMapNaviView.swift +2331 -201
  207. package/ios/ExpoGaodeMapNaviViewModule.swift +109 -1
  208. package/ios/ExpoGaodeMapNavigation.podspec +2 -1
  209. package/ios/ExpoGaodeMapNavigationModule.swift +7 -5
  210. package/ios/managers/IndependentRouteManager.swift +90 -26
  211. package/ios/map/ExpoGaodeMapModule.swift +72 -21
  212. package/ios/map/ExpoGaodeMapOfflineModule.swift +61 -0
  213. package/ios/map/ExpoGaodeMapSearchModule.swift +773 -0
  214. package/ios/map/ExpoGaodeMapView.swift +23 -5
  215. package/ios/map/GaodeMapPrivacyManager.swift +26 -18
  216. package/ios/map/cpp/GeometryEngine.cpp +112 -0
  217. package/ios/map/cpp/GeometryEngine.hpp +21 -0
  218. package/ios/map/modules/LocationManager.swift +37 -5
  219. package/ios/map/overlays/MarkerView.swift +11 -11
  220. package/ios/map/overlays/MarkerViewModule.swift +4 -4
  221. package/ios/map/overlays/PolylineView.swift +6 -12
  222. package/ios/map/utils/ClusterNative.h +8 -0
  223. package/ios/map/utils/ClusterNative.mm +27 -0
  224. package/ios/map/utils/PermissionManager.swift +115 -6
  225. package/ios/routes/drive/DriveTruckRouteCalculator.swift +165 -77
  226. package/ios/routes/walkride/WalkRideRouteCalculator.swift +127 -1
  227. package/ios/services/IndependentRouteService.swift +198 -39
  228. package/ios/services/NavigationLiveActivityAttributes.swift +48 -0
  229. package/ios/services/NavigationLiveActivityManager.swift +359 -0
  230. package/package.json +22 -7
  231. package/plugin/build/withGaodeMap.d.ts +8 -0
  232. package/plugin/build/withGaodeMap.js +60 -4
  233. package/scripts/check-expo-modules.js +68 -0
  234. package/shared/cpp/GeometryEngine.cpp +112 -0
  235. package/shared/cpp/GeometryEngine.hpp +21 -0
  236. package/widget-template/README.md +46 -0
  237. package/widget-template/ios/NavigationLiveActivityWidget.swift +367 -0
  238. package/android/src/main/java/expo/modules/gaodemap/navigation/managers/RouteCalculator.kt +0 -173
@@ -0,0 +1,661 @@
1
+ package expo.modules.gaodemap.navigation.services
2
+
3
+ import android.Manifest
4
+ import android.annotation.SuppressLint
5
+ import android.app.Notification
6
+ import android.app.NotificationChannel
7
+ import android.app.NotificationManager
8
+ import android.app.PendingIntent
9
+ import android.app.Service
10
+ import android.content.Context
11
+ import android.content.Intent
12
+ import android.content.pm.PackageManager
13
+ import android.graphics.Bitmap
14
+ import android.graphics.BitmapFactory
15
+ import android.graphics.Canvas
16
+ import android.graphics.Color
17
+ import android.graphics.Matrix
18
+ import android.graphics.Paint
19
+ import android.graphics.Typeface
20
+ import android.os.Build
21
+ import android.os.Bundle
22
+ import android.os.IBinder
23
+ import android.net.Uri
24
+ import android.util.Log
25
+ import androidx.annotation.RequiresApi
26
+ import androidx.core.app.NotificationCompat
27
+ import androidx.core.content.ContextCompat
28
+ import androidx.core.graphics.drawable.IconCompat
29
+ import expo.modules.gaodemap.navigation.R
30
+ import kotlin.math.ceil
31
+ import kotlin.math.roundToInt
32
+ import androidx.core.graphics.createBitmap
33
+ import androidx.core.graphics.toColorInt
34
+
35
+ internal data class NavigationNotificationSnapshot(
36
+ val currentRoadName: String? = null,
37
+ val nextRoadName: String? = null,
38
+ val pathRetainDistance: Int? = null,
39
+ val routeTotalDistance: Int? = null,
40
+ val pathRetainTime: Int? = null,
41
+ val curStepRetainDistance: Int? = null,
42
+ val iconType: Int? = null,
43
+ val turnIconImageUri: String? = null
44
+ )
45
+
46
+ class NavigationForegroundService : Service() {
47
+ companion object {
48
+ private const val TAG = "NavigationForegroundService"
49
+ private const val NOTIFICATION_ID = 1002
50
+ private const val CHANNEL_ID = "navigation_service_channel"
51
+ private const val CHANNEL_NAME = "导航进行中"
52
+ private const val PERMISSION_POST_PROMOTED_NOTIFICATIONS = "android.permission.POST_PROMOTED_NOTIFICATIONS"
53
+
54
+ private const val ACTION_START_OR_UPDATE = "expo.modules.gaodemap.navigation.action.START_OR_UPDATE"
55
+
56
+ private const val EXTRA_CURRENT_ROAD = "extra_current_road"
57
+ private const val EXTRA_NEXT_ROAD = "extra_next_road"
58
+ private const val EXTRA_PATH_RETAIN_DISTANCE = "extra_path_retain_distance"
59
+ private const val EXTRA_ROUTE_TOTAL_DISTANCE = "extra_route_total_distance"
60
+ private const val EXTRA_PATH_RETAIN_TIME = "extra_path_retain_time"
61
+ private const val EXTRA_STEP_RETAIN_DISTANCE = "extra_step_retain_distance"
62
+ private const val EXTRA_ICON_TYPE = "extra_icon_type"
63
+ private const val EXTRA_TURN_ICON_IMAGE_URI = "extra_turn_icon_image_uri"
64
+ @Volatile
65
+ private var cachedRouteTotalDistanceMeters: Int? = null
66
+
67
+ internal fun startOrUpdate(context: Context, snapshot: NavigationNotificationSnapshot?) {
68
+ val appContext = context.applicationContext
69
+ snapshot?.routeTotalDistance
70
+ ?.takeIf { it > 0 }
71
+ ?.let { cachedRouteTotalDistanceMeters = it }
72
+ Log.d(
73
+ TAG,
74
+ "startOrUpdate called: stepDistance=${snapshot?.curStepRetainDistance}, " +
75
+ "remainDistance=${snapshot?.pathRetainDistance}, routeTotal=${snapshot?.routeTotalDistance}, " +
76
+ "remainTime=${snapshot?.pathRetainTime}, " +
77
+ "nextRoad=${snapshot?.nextRoadName}, turnIconUri=${snapshot?.turnIconImageUri}"
78
+ )
79
+ val intent = Intent(appContext, NavigationForegroundService::class.java).apply {
80
+ action = ACTION_START_OR_UPDATE
81
+ snapshot?.let { payload ->
82
+ putExtra(EXTRA_CURRENT_ROAD, payload.currentRoadName)
83
+ putExtra(EXTRA_NEXT_ROAD, payload.nextRoadName)
84
+ payload.pathRetainDistance?.let { putExtra(EXTRA_PATH_RETAIN_DISTANCE, it) }
85
+ payload.routeTotalDistance?.let { putExtra(EXTRA_ROUTE_TOTAL_DISTANCE, it) }
86
+ payload.pathRetainTime?.let { putExtra(EXTRA_PATH_RETAIN_TIME, it) }
87
+ payload.curStepRetainDistance?.let { putExtra(EXTRA_STEP_RETAIN_DISTANCE, it) }
88
+ payload.iconType?.let { putExtra(EXTRA_ICON_TYPE, it) }
89
+ payload.turnIconImageUri?.let { putExtra(EXTRA_TURN_ICON_IMAGE_URI, it) }
90
+ }
91
+ }
92
+
93
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
94
+ appContext.startForegroundService(intent)
95
+ } else {
96
+ appContext.startService(intent)
97
+ }
98
+ }
99
+
100
+ internal fun stop(context: Context) {
101
+ val appContext = context.applicationContext
102
+ Log.d(TAG, "stop called")
103
+ appContext.stopService(Intent(appContext, NavigationForegroundService::class.java))
104
+ }
105
+
106
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
107
+ private fun hasNotificationPermission(context: Context): Boolean {
108
+ return ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS)== PackageManager.PERMISSION_GRANTED
109
+ }
110
+ }
111
+
112
+ private var latestSnapshot = NavigationNotificationSnapshot()
113
+ private var isForeground = false
114
+ private var routeTotalDistanceMeters: Int? = null
115
+ private val trackerIconCache = mutableMapOf<Int, IconCompat>()
116
+ private var customCarTrackerIcon: IconCompat? = null
117
+
118
+ override fun onBind(intent: Intent?): IBinder? = null
119
+
120
+ override fun onCreate() {
121
+ super.onCreate()
122
+ routeTotalDistanceMeters = cachedRouteTotalDistanceMeters
123
+ Log.d(TAG, "onCreate")
124
+ createNotificationChannel()
125
+ }
126
+
127
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
128
+ Log.d(TAG, "onStartCommand action=${intent?.action}, startId=$startId, flags=$flags")
129
+ if (intent?.action != ACTION_START_OR_UPDATE) {
130
+ Log.d(TAG, "Ignoring action=${intent?.action}")
131
+ return START_NOT_STICKY
132
+ }
133
+
134
+ latestSnapshot = latestSnapshot.mergeFrom(intent)
135
+ Log.d(
136
+ TAG,
137
+ "merged snapshot: stepDistance=${latestSnapshot.curStepRetainDistance}, " +
138
+ "remainDistance=${latestSnapshot.pathRetainDistance}, routeTotal=${latestSnapshot.routeTotalDistance}, " +
139
+ "remainTime=${latestSnapshot.pathRetainTime}, " +
140
+ "currentRoad=${latestSnapshot.currentRoadName}, nextRoad=${latestSnapshot.nextRoadName}, " +
141
+ "turnIconUri=${latestSnapshot.turnIconImageUri}"
142
+ )
143
+ // Android 13+ forbids posting most notifications without POST_NOTIFICATIONS,
144
+ // but foreground services still must call startForeground() in time.
145
+ // If the permission is denied, the ongoing notice is still surfaced in Task Manager
146
+ // even though it may be hidden from the notification drawer.
147
+ val hasPermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
148
+ hasNotificationPermission(this)
149
+ if (!hasPermission) {
150
+ Log.w(
151
+ TAG,
152
+ "POST_NOTIFICATIONS not granted; foreground service notification may be hidden from the drawer."
153
+ )
154
+ }
155
+
156
+ val notification = buildNotification(latestSnapshot)
157
+
158
+ if (!isForeground) {
159
+ try {
160
+ startForeground(NOTIFICATION_ID, notification)
161
+ isForeground = true
162
+ Log.d(TAG, "startForeground success, notificationId=$NOTIFICATION_ID")
163
+ } catch (e: Exception) {
164
+ Log.e(TAG, "Failed to start foreground service: ${e.message}", e)
165
+ stopSelf()
166
+ return START_NOT_STICKY
167
+ }
168
+ } else {
169
+ getNotificationManager().notify(NOTIFICATION_ID, notification)
170
+ Log.d(TAG, "notify update pushed, notificationId=$NOTIFICATION_ID")
171
+ }
172
+
173
+ return START_STICKY
174
+ }
175
+
176
+ override fun onDestroy() {
177
+ Log.d(TAG, "onDestroy isForeground=$isForeground")
178
+ if (isForeground) {
179
+ stopForeground(STOP_FOREGROUND_REMOVE)
180
+ isForeground = false
181
+ }
182
+ super.onDestroy()
183
+ }
184
+
185
+ private fun buildNotification(snapshot: NavigationNotificationSnapshot): Notification {
186
+ val appName = resolveAppName()
187
+ val maneuverText = resolveManeuverText(snapshot.iconType)
188
+ val instructionText = buildInstructionText(snapshot, maneuverText)
189
+ val roadText = buildRoadText(snapshot, fallback = "导航进行中")
190
+ val routeSummary = buildRouteSummaryText(snapshot)
191
+ val shortCriticalText = buildShortCriticalText(snapshot, maneuverText)
192
+ val pendingIntent = createLaunchPendingIntent()
193
+ val extras = Bundle().apply {
194
+ // Keep detailed navigation payload in extras for OEM/system smart-surface readers.
195
+ putString("expo_gaode_nav_instruction", instructionText)
196
+ putString("expo_gaode_nav_summary", routeSummary)
197
+ putInt("expo_gaode_nav_remain_distance", snapshot.pathRetainDistance ?: -1)
198
+ putInt("expo_gaode_nav_remain_time", snapshot.pathRetainTime ?: -1)
199
+ putInt("expo_gaode_nav_step_distance", snapshot.curStepRetainDistance ?: -1)
200
+ putInt("expo_gaode_nav_icon_type", snapshot.iconType ?: -1)
201
+ putString("expo_gaode_nav_short_critical_text", shortCriticalText)
202
+ }
203
+ val requestPromotedOngoing = shouldRequestPromotedOngoing()
204
+ val canPostPromoted = canPostPromotedNotificationsCompat()
205
+ val style = buildProgressStyle(snapshot)
206
+ Log.d(
207
+ TAG,
208
+ "buildNotification promotedRequested=$requestPromotedOngoing, canPostPromoted=$canPostPromoted, sdkInt=${Build.VERSION.SDK_INT}"
209
+ )
210
+
211
+ val builder = NotificationCompat.Builder(this, CHANNEL_ID)
212
+ .setContentTitle(instructionText)
213
+ .setContentText(roadText)
214
+ .setSubText(appName)
215
+ .setStyle(style)
216
+ .setSmallIcon(resolveNotificationSmallIconResId())
217
+ .setLargeIcon(resolveManeuverLargeIconBitmap(snapshot))
218
+ .setPriority(NotificationCompat.PRIORITY_LOW)
219
+ .setCategory(NotificationCompat.CATEGORY_NAVIGATION)
220
+ .setOnlyAlertOnce(true)
221
+ .setOngoing(true)
222
+ .setSilent(true)
223
+ .addExtras(extras)
224
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
225
+ .setRequestPromotedOngoing(requestPromotedOngoing)
226
+ .setShortCriticalText(shortCriticalText)
227
+
228
+ if (pendingIntent != null) {
229
+ builder.setContentIntent(pendingIntent)
230
+ }
231
+ val notification = builder.build()
232
+ logPromotionDiagnostics(
233
+ notification = notification,
234
+ requested = requestPromotedOngoing,
235
+ canPostPromoted = canPostPromoted
236
+ )
237
+ return notification
238
+ }
239
+
240
+ private fun buildProgressStyle(snapshot: NavigationNotificationSnapshot): NotificationCompat.Style {
241
+ val style = NotificationCompat.ProgressStyle()
242
+ .setStyledByProgress(true)
243
+ .setProgressSegments(
244
+ listOf(
245
+ NotificationCompat.ProgressStyle.Segment(33).setColor("#4B63FF".toColorInt()),
246
+ NotificationCompat.ProgressStyle.Segment(33).setColor("#4B63FF".toColorInt()),
247
+ NotificationCompat.ProgressStyle.Segment(34).setColor("#4B63FF".toColorInt())
248
+ )
249
+ )
250
+ .setProgressPoints(
251
+ listOf(
252
+ NotificationCompat.ProgressStyle.Point(33).setColor("#BAC7FF".toColorInt()),
253
+ NotificationCompat.ProgressStyle.Point(66).setColor("#BAC7FF".toColorInt()),
254
+ NotificationCompat.ProgressStyle.Point(100).setColor("#BAC7FF".toColorInt())
255
+ )
256
+ )
257
+
258
+ (resolveTurnTrackerIcon(snapshot) ?: resolveDefaultTrackerIcon())
259
+ ?.let { style.setProgressTrackerIcon(it) }
260
+
261
+ val remainDistance = snapshot.pathRetainDistance
262
+ if (remainDistance == null || remainDistance <= 0) {
263
+ style.setProgressIndeterminate(true)
264
+ Log.d(TAG, "progress style indeterminate: remainDistance=$remainDistance")
265
+ return style
266
+ }
267
+
268
+ val previousTotal = routeTotalDistanceMeters ?: 0
269
+ val cachedTotal = cachedRouteTotalDistanceMeters ?: 0
270
+ val snapshotTotal = snapshot.routeTotalDistance?.coerceAtLeast(0) ?: 0
271
+ val updatedTotal = maxOf(previousTotal, cachedTotal, snapshotTotal, remainDistance)
272
+ routeTotalDistanceMeters = updatedTotal
273
+ if (updatedTotal > 0) {
274
+ cachedRouteTotalDistanceMeters = updatedTotal
275
+ }
276
+ val progress = (updatedTotal - remainDistance).coerceAtLeast(0)
277
+ val progressPercent = ((progress.toFloat() / updatedTotal.toFloat()) * 100f)
278
+ .coerceIn(0f, 100f)
279
+ .roundToInt()
280
+ style
281
+ .setProgressIndeterminate(false)
282
+ .setProgress(progressPercent)
283
+
284
+ Log.d(
285
+ TAG,
286
+ "progress style determinate: progress=$progress, max=$updatedTotal, remainDistance=$remainDistance, " +
287
+ "progressPercent=$progressPercent"
288
+ )
289
+ return style
290
+ }
291
+
292
+ private fun resolveAppName(): String {
293
+ return try {
294
+ packageManager.getApplicationLabel(applicationInfo).toString()
295
+ .takeIf { it.isNotBlank() }
296
+ ?: "导航"
297
+ } catch (_: Throwable) {
298
+ "导航"
299
+ }
300
+ }
301
+
302
+ private fun resolveNotificationSmallIconResId(): Int {
303
+ return R.drawable.ic_nav_notification_small
304
+ }
305
+
306
+ private fun resolveManeuverLargeIconBitmap(snapshot: NavigationNotificationSnapshot): Bitmap? {
307
+ val fromUri = resolveTurnIconBitmapFromUri(snapshot.turnIconImageUri)
308
+ if (fromUri != null) {
309
+ return fromUri
310
+ }
311
+
312
+ val iconType = snapshot.iconType ?: return null
313
+ return runCatching { createTurnTrackerBitmap(iconType) }
314
+ .onFailure { Log.w(TAG, "Failed to build maneuver large icon bitmap", it) }
315
+ .getOrNull()
316
+ }
317
+
318
+ private fun resolveTurnIconBitmapFromUri(uriString: String?): Bitmap? {
319
+ val uri = uriString?.takeIf { it.isNotBlank() } ?: return null
320
+ return try {
321
+ contentResolver.openInputStream(Uri.parse(uri))?.use { input ->
322
+ BitmapFactory.decodeStream(input)
323
+ }
324
+ } catch (error: Throwable) {
325
+ Log.w(TAG, "Failed to decode turn icon bitmap from uri=$uri", error)
326
+ null
327
+ }
328
+ }
329
+
330
+ @SuppressLint("UseKtx")
331
+ private fun resolveTurnTrackerIcon(snapshot: NavigationNotificationSnapshot): IconCompat? {
332
+ resolveCustomCarTrackerIcon()?.let { return it }
333
+
334
+ resolveTurnTrackerIconFromIconType(snapshot.iconType)?.let {
335
+ return it
336
+ }
337
+
338
+ val uri = snapshot.turnIconImageUri?.takeIf { it.isNotBlank() } ?: return null
339
+ return try {
340
+ Log.d(TAG, "Using turn tracker icon from uri=$uri")
341
+ IconCompat.createWithContentUri(uri)
342
+ } catch (error: Throwable) {
343
+ Log.w(TAG, "Failed to resolve turn tracker icon from uri=$uri", error)
344
+ null
345
+ }
346
+ }
347
+
348
+ private fun resolveCustomCarTrackerIcon(): IconCompat? {
349
+ customCarTrackerIcon?.let { return it }
350
+ return try {
351
+ val bitmap = BitmapFactory.decodeResource(resources, R.drawable.nav_tracker_car)
352
+ if (bitmap == null) {
353
+ Log.w(TAG, "Failed to decode nav_tracker_car bitmap")
354
+ return null
355
+ }
356
+ val rotated = rotateBitmap(bitmap, 90f)
357
+ IconCompat.createWithBitmap(rotated).also {
358
+ customCarTrackerIcon = it
359
+ Log.d(TAG, "Using custom car tracker icon from drawable nav_tracker_car (rotated 90deg)")
360
+ }
361
+ } catch (error: Throwable) {
362
+ Log.w(TAG, "Failed to load custom car tracker icon, fallback to default logic", error)
363
+ null
364
+ }
365
+ }
366
+
367
+ private fun rotateBitmap(source: Bitmap, angleDegrees: Float): Bitmap {
368
+ val matrix = Matrix().apply { postRotate(angleDegrees) }
369
+ return Bitmap.createBitmap(source, 0, 0, source.width, source.height, matrix, true)
370
+ }
371
+
372
+ private fun resolveTurnTrackerIconFromIconType(iconType: Int?): IconCompat? {
373
+ val safeIconType = iconType ?: return null
374
+ trackerIconCache[safeIconType]?.let { return it }
375
+
376
+ return try {
377
+ val icon = IconCompat.createWithBitmap(createTurnTrackerBitmap(safeIconType))
378
+ trackerIconCache[safeIconType] = icon
379
+ Log.d(TAG, "Using generated turn tracker icon from iconType=$safeIconType")
380
+ icon
381
+ } catch (error: Throwable) {
382
+ Log.w(TAG, "Failed to generate tracker icon for iconType=$safeIconType", error)
383
+ null
384
+ }
385
+ }
386
+
387
+ private fun createTurnTrackerBitmap(iconType: Int): Bitmap {
388
+ val sizePx = 72
389
+ val bitmap = createBitmap(sizePx, sizePx)
390
+ val canvas = Canvas(bitmap)
391
+
392
+ val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
393
+ color = "#4556A8".toColorInt()
394
+ style = Paint.Style.FILL
395
+ }
396
+ val radius = sizePx * 0.24f
397
+ canvas.drawRoundRect(0f, 0f, sizePx.toFloat(), sizePx.toFloat(), radius, radius, bgPaint)
398
+
399
+ val arrow = resolveArrowGlyph(iconType)
400
+ val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
401
+ color = Color.WHITE
402
+ textAlign = Paint.Align.CENTER
403
+ textSize = sizePx * 0.52f
404
+ typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
405
+ }
406
+ val y = sizePx / 2f - (textPaint.descent() + textPaint.ascent()) / 2f
407
+ canvas.drawText(arrow, sizePx / 2f, y, textPaint)
408
+ return bitmap
409
+ }
410
+
411
+ private fun resolveArrowGlyph(iconType: Int): String {
412
+ return when (iconType) {
413
+ 2 -> "←"
414
+ 3 -> "→"
415
+ 4 -> "↖"
416
+ 5 -> "↗"
417
+ 6 -> "↙"
418
+ 7 -> "↘"
419
+ 8 -> "↶"
420
+ 9 -> "↑"
421
+ 15, 31 -> "⇦"
422
+ 16, 32 -> "⇨"
423
+ else -> "↑"
424
+ }
425
+ }
426
+
427
+ private fun resolveDefaultTrackerIcon(): IconCompat? {
428
+ return try {
429
+ IconCompat.createWithResource(this, android.R.drawable.ic_menu_directions)
430
+ } catch (error: Throwable) {
431
+ Log.w(TAG, "Failed to resolve default tracker icon", error)
432
+ null
433
+ }
434
+ }
435
+
436
+ private fun resolveManeuverText(iconType: Int?): String {
437
+ return when (iconType ?: -1) {
438
+ 2 -> "左转"
439
+ 3 -> "右转"
440
+ 4 -> "向左前方行驶"
441
+ 5 -> "向右前方行驶"
442
+ 6 -> "向左后方行驶"
443
+ 7 -> "向右后方行驶"
444
+ 8 -> "调头"
445
+ 9 -> "直行"
446
+ 11 -> "进入环岛"
447
+ 12 -> "驶出环岛"
448
+ 14 -> "左转弯待转"
449
+ 15, 31 -> "靠左行驶"
450
+ 16, 32 -> "靠右行驶"
451
+ 17 -> "进入隧道"
452
+ 29 -> "进入匝道"
453
+ 30 -> "驶出匝道"
454
+ else -> ""
455
+ }
456
+ }
457
+
458
+ private fun buildShortCriticalText(snapshot: NavigationNotificationSnapshot, maneuverText: String): String {
459
+ val stepDistance = snapshot.curStepRetainDistance?.let { formatDistance(it) }
460
+ if (!stepDistance.isNullOrBlank() && maneuverText.isNotBlank()) {
461
+ return "$stepDistance $maneuverText"
462
+ }
463
+ if (!stepDistance.isNullOrBlank()) {
464
+ return "${stepDistance}后继续"
465
+ }
466
+ if (maneuverText.isNotBlank()) {
467
+ return maneuverText
468
+ }
469
+ val remainDistance = snapshot.pathRetainDistance
470
+ return if (remainDistance != null) {
471
+ formatDistance(remainDistance)
472
+ } else {
473
+ "导航中"
474
+ }
475
+ }
476
+
477
+ private fun shouldRequestPromotedOngoing(): Boolean {
478
+ // 1. 必须是 Android 13+
479
+ if (Build.VERSION.SDK_INT < 33) {
480
+ Log.d(TAG, "promoted disabled: sdkInt=${Build.VERSION.SDK_INT} < 33")
481
+ return false
482
+ }
483
+ // 2. 检查运行时权限(关键修改)
484
+ if (!hasNotificationPermission(this)) {
485
+ Log.w(TAG, "Missing POST_NOTIFICATIONS runtime permission. Falling back to normal notification.")
486
+ return false
487
+ }
488
+ if (Build.VERSION.SDK_INT >= 36 && !hasManifestPermission(PERMISSION_POST_PROMOTED_NOTIFICATIONS)) {
489
+ Log.w(TAG, "Missing POST_PROMOTED_NOTIFICATIONS in manifest. Cannot request promoted ongoing.")
490
+ return false
491
+ }
492
+ return true
493
+ }
494
+
495
+ private fun hasManifestPermission(permission: String): Boolean {
496
+ return try {
497
+ packageManager.checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED
498
+ } catch (_: Throwable) {
499
+ false
500
+ }
501
+ }
502
+
503
+ private fun canPostPromotedNotificationsCompat(): Boolean? {
504
+ if (Build.VERSION.SDK_INT < 36) {
505
+ return null
506
+ }
507
+ return try {
508
+ canPostPromotedNotificationsApi36(getNotificationManager())
509
+ } catch (error: Throwable) {
510
+ Log.w(TAG, "canPostPromotedNotifications check failed", error)
511
+ null
512
+ }
513
+ }
514
+
515
+ @RequiresApi(36)
516
+ private fun canPostPromotedNotificationsApi36(manager: NotificationManager): Boolean {
517
+ return manager.canPostPromotedNotifications()
518
+ }
519
+
520
+ private fun logPromotionDiagnostics(
521
+ notification: Notification,
522
+ requested: Boolean,
523
+ canPostPromoted: Boolean?
524
+ ) {
525
+ val hasPromotableCharacteristics = if (Build.VERSION.SDK_INT >= 36) {
526
+ hasPromotableCharacteristicsApi36(notification)
527
+ } else {
528
+ null
529
+ }
530
+ val promotedFlagEnabled = if (Build.VERSION.SDK_INT >= 36) {
531
+ isPromotedFlagEnabledApi36(notification)
532
+ } else {
533
+ null
534
+ }
535
+
536
+ Log.d(
537
+ TAG,
538
+ "promotion diagnostics: requested=$requested, canPostPromoted=$canPostPromoted, promotable=$hasPromotableCharacteristics, promotedFlag=$promotedFlagEnabled"
539
+ )
540
+ }
541
+
542
+ @RequiresApi(36)
543
+ private fun hasPromotableCharacteristicsApi36(notification: Notification): Boolean {
544
+ return notification.hasPromotableCharacteristics()
545
+ }
546
+
547
+ @RequiresApi(36)
548
+ private fun isPromotedFlagEnabledApi36(notification: Notification): Boolean {
549
+ return (notification.flags and Notification.FLAG_PROMOTED_ONGOING) != 0
550
+ }
551
+
552
+ private fun createLaunchPendingIntent(): PendingIntent? {
553
+ val launchIntent = packageManager.getLaunchIntentForPackage(packageName) ?: return null
554
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
555
+ val flags =
556
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
557
+ return PendingIntent.getActivity(this, 0, launchIntent, flags)
558
+ }
559
+
560
+ private fun buildInstructionText(snapshot: NavigationNotificationSnapshot, maneuverText: String): String {
561
+ val stepDistanceText = snapshot.curStepRetainDistance?.let { formatDistance(it) }
562
+ if (!stepDistanceText.isNullOrBlank() && maneuverText.isNotBlank()) {
563
+ return "${stepDistanceText}后$maneuverText"
564
+ }
565
+ if (!stepDistanceText.isNullOrBlank()) {
566
+ return "${stepDistanceText}后执行下一步"
567
+ }
568
+ if (maneuverText.isNotBlank()) {
569
+ return maneuverText
570
+ }
571
+ return "导航进行中"
572
+ }
573
+
574
+ private fun buildRoadText(snapshot: NavigationNotificationSnapshot, fallback: String): String {
575
+ val nextRoad = snapshot.nextRoadName?.trim().orEmpty()
576
+ if (nextRoad.isNotBlank()) {
577
+ return nextRoad
578
+ }
579
+ val currentRoad = snapshot.currentRoadName?.trim().orEmpty()
580
+ if (currentRoad.isNotBlank()) {
581
+ return currentRoad
582
+ }
583
+ return fallback
584
+ }
585
+
586
+ private fun buildRouteSummaryText(snapshot: NavigationNotificationSnapshot): String {
587
+ val retainDistanceText = snapshot.pathRetainDistance?.let { formatDistance(it) } ?: "--"
588
+ val retainTimeText = snapshot.pathRetainTime?.let { formatDuration(it) } ?: "--"
589
+ val road = snapshot.currentRoadName?.trim().orEmpty().ifBlank { "前方道路" }
590
+ return "剩余${retainDistanceText} · 约${retainTimeText} · $road"
591
+ }
592
+
593
+ @SuppressLint("DefaultLocale")
594
+ private fun formatDistance(distanceMeters: Int): String {
595
+ return if (distanceMeters < 1000) {
596
+ "${distanceMeters.coerceAtLeast(0)}米"
597
+ } else {
598
+ String.format("%.1f公里", distanceMeters / 1000.0)
599
+ }
600
+ }
601
+
602
+ private fun formatDuration(durationSeconds: Int): String {
603
+ val safeSeconds = durationSeconds.coerceAtLeast(0)
604
+ if (safeSeconds < 3600) {
605
+ return "${ceil(safeSeconds / 60.0).toInt()}分钟"
606
+ }
607
+
608
+ val hours = safeSeconds / 3600
609
+ val minutes = ceil((safeSeconds % 3600) / 60.0).toInt().coerceAtMost(59)
610
+ return "${hours}小时${minutes}分钟"
611
+ }
612
+
613
+ private fun createNotificationChannel() {
614
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
615
+ return
616
+ }
617
+
618
+ val channel = NotificationChannel(
619
+ CHANNEL_ID,
620
+ CHANNEL_NAME,
621
+ NotificationManager.IMPORTANCE_DEFAULT
622
+ ).apply {
623
+ description = "导航进行中通知"
624
+ setShowBadge(false)
625
+ }
626
+ getNotificationManager().createNotificationChannel(channel)
627
+ Log.d(TAG, "notification channel ensured: id=$CHANNEL_ID, importance=${channel.importance}")
628
+ }
629
+
630
+ private fun getNotificationManager(): NotificationManager {
631
+ return getSystemService(NOTIFICATION_SERVICE) as NotificationManager
632
+ }
633
+
634
+ private fun NavigationNotificationSnapshot.mergeFrom(intent: Intent): NavigationNotificationSnapshot {
635
+ return copy(
636
+ currentRoadName = intent.getStringExtra(EXTRA_CURRENT_ROAD) ?: currentRoadName,
637
+ nextRoadName = intent.getStringExtra(EXTRA_NEXT_ROAD) ?: nextRoadName,
638
+ pathRetainDistance = if (intent.hasExtra(EXTRA_PATH_RETAIN_DISTANCE)) {
639
+ intent.getIntExtra(EXTRA_PATH_RETAIN_DISTANCE, pathRetainDistance ?: 0)
640
+ } else {
641
+ pathRetainDistance
642
+ },
643
+ pathRetainTime = if (intent.hasExtra(EXTRA_PATH_RETAIN_TIME)) {
644
+ intent.getIntExtra(EXTRA_PATH_RETAIN_TIME, pathRetainTime ?: 0)
645
+ } else {
646
+ pathRetainTime
647
+ },
648
+ curStepRetainDistance = if (intent.hasExtra(EXTRA_STEP_RETAIN_DISTANCE)) {
649
+ intent.getIntExtra(EXTRA_STEP_RETAIN_DISTANCE, curStepRetainDistance ?: 0)
650
+ } else {
651
+ curStepRetainDistance
652
+ },
653
+ iconType = if (intent.hasExtra(EXTRA_ICON_TYPE)) {
654
+ intent.getIntExtra(EXTRA_ICON_TYPE, iconType ?: 0)
655
+ } else {
656
+ iconType
657
+ },
658
+ turnIconImageUri = intent.getStringExtra(EXTRA_TURN_ICON_IMAGE_URI) ?: turnIconImageUri
659
+ )
660
+ }
661
+ }