@javascriptcommon/react-native-carplay 2.3.11 → 2.4.1-beta.0
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.
- package/android/build.gradle +110 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/android/gradle.properties +20 -0
- package/android/gradlew +234 -0
- package/android/gradlew.bat +89 -0
- package/android/src/main/AndroidManifest.xml +30 -0
- package/android/src/main/AndroidManifestNew.xml +30 -0
- package/android/src/main/java/org/birkir/carplay/CarPlayModule.kt +321 -0
- package/android/src/main/java/org/birkir/carplay/CarPlayPackage.kt +18 -0
- package/android/src/main/java/org/birkir/carplay/CarPlayService.kt +35 -0
- package/android/src/main/java/org/birkir/carplay/CarPlaySession.kt +126 -0
- package/android/src/main/java/org/birkir/carplay/parser/Ext.kt +11 -0
- package/android/src/main/java/org/birkir/carplay/parser/Parser.kt +18 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTGridTemplate.kt +28 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTListTemplate.kt +64 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTMapTemplate.kt +128 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTMessageTemplate.kt +28 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTPaneTemplate.kt +24 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTSearchTemplate.kt +41 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTTabTemplate.kt +157 -0
- package/android/src/main/java/org/birkir/carplay/parser/RCTTemplate.kt +419 -0
- package/android/src/main/java/org/birkir/carplay/parser/TemplateParser.kt +35 -0
- package/android/src/main/java/org/birkir/carplay/screens/CarScreen.kt +76 -0
- package/android/src/main/java/org/birkir/carplay/screens/CarScreenContext.kt +10 -0
- package/android/src/main/java/org/birkir/carplay/utils/EventEmitter.kt +167 -0
- package/android/src/main/java/org/birkir/carplay/utils/VirtualRenderer.kt +75 -0
- package/ios/RCTConvert+RNCarPlay.h +7 -8
- package/ios/RCTConvert+RNCarPlay.m +3 -2
- package/ios/RNCarPlay.h +11 -14
- package/ios/RNCarPlay.m +749 -945
- package/ios/RNCarPlayViewController.h +10 -0
- package/ios/RNCarPlayViewController.m +50 -0
- package/lib/CarPlay.d.ts +183 -0
- package/lib/CarPlay.d.ts.map +1 -0
- package/lib/index.d.ts +44 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/interfaces/Action.d.ts +13 -0
- package/lib/interfaces/Action.d.ts.map +1 -0
- package/lib/interfaces/AlertAction.d.ts +6 -0
- package/lib/interfaces/AlertAction.d.ts.map +1 -0
- package/lib/interfaces/BarButton.d.ts +39 -0
- package/lib/interfaces/BarButton.d.ts.map +1 -0
- package/lib/interfaces/CarColor.d.ts +2 -0
- package/lib/interfaces/CarColor.d.ts.map +1 -0
- package/lib/interfaces/GridButton.d.ts +24 -0
- package/lib/interfaces/GridButton.d.ts.map +1 -0
- package/lib/interfaces/Header.d.ts +15 -0
- package/lib/interfaces/Header.d.ts.map +1 -0
- package/lib/interfaces/ListItem.d.ts +90 -0
- package/lib/interfaces/ListItem.d.ts.map +1 -0
- package/lib/interfaces/ListItemUpdate.d.ts +15 -0
- package/lib/interfaces/ListItemUpdate.d.ts.map +1 -0
- package/lib/interfaces/ListSection.d.ts +21 -0
- package/lib/interfaces/ListSection.d.ts.map +1 -0
- package/lib/interfaces/Maneuver.d.ts +31 -0
- package/lib/interfaces/Maneuver.d.ts.map +1 -0
- package/lib/interfaces/MapButton.d.ts +27 -0
- package/lib/interfaces/MapButton.d.ts.map +1 -0
- package/lib/interfaces/NavigationAlert.d.ts +44 -0
- package/lib/interfaces/NavigationAlert.d.ts.map +1 -0
- package/lib/interfaces/NavigationInfo.d.ts +17 -0
- package/lib/interfaces/NavigationInfo.d.ts.map +1 -0
- package/lib/interfaces/NavigationStep.d.ts +17 -0
- package/lib/interfaces/NavigationStep.d.ts.map +1 -0
- package/lib/interfaces/Pane.d.ts +29 -0
- package/lib/interfaces/Pane.d.ts.map +1 -0
- package/lib/interfaces/PauseReason.d.ts +8 -0
- package/lib/interfaces/PauseReason.d.ts.map +1 -0
- package/lib/interfaces/Place.d.ts +11 -0
- package/lib/interfaces/Place.d.ts.map +1 -0
- package/lib/interfaces/TextConfiguration.d.ts +6 -0
- package/lib/interfaces/TextConfiguration.d.ts.map +1 -0
- package/lib/interfaces/TimeRemainingColor.d.ts +2 -0
- package/lib/interfaces/TimeRemainingColor.d.ts.map +1 -0
- package/lib/interfaces/TravelEstimates.d.ts +37 -0
- package/lib/interfaces/TravelEstimates.d.ts.map +1 -0
- package/lib/interfaces/VoiceControlState.d.ts +8 -0
- package/lib/interfaces/VoiceControlState.d.ts.map +1 -0
- package/lib/navigation/NavigationSession.d.ts +18 -0
- package/lib/navigation/NavigationSession.d.ts.map +1 -0
- package/lib/navigation/Trip.d.ts +22 -0
- package/lib/navigation/Trip.d.ts.map +1 -0
- package/lib/templates/ActionSheetTemplate.d.ts +18 -0
- package/lib/templates/ActionSheetTemplate.d.ts.map +1 -0
- package/lib/templates/AlertTemplate.d.ts +17 -0
- package/lib/templates/AlertTemplate.d.ts.map +1 -0
- package/lib/templates/ContactTemplate.d.ts +36 -0
- package/lib/templates/ContactTemplate.d.ts.map +1 -0
- package/lib/templates/GridTemplate.d.ts +38 -0
- package/lib/templates/GridTemplate.d.ts.map +1 -0
- package/lib/templates/InformationTemplate.d.ts +28 -0
- package/lib/templates/InformationTemplate.d.ts.map +1 -0
- package/lib/templates/ListTemplate.d.ts +127 -0
- package/lib/templates/ListTemplate.d.ts.map +1 -0
- package/lib/templates/ListTemplate.js +24 -16
- package/lib/templates/MapTemplate.d.ts +171 -0
- package/lib/templates/MapTemplate.d.ts.map +1 -0
- package/lib/templates/NowPlayingTemplate.d.ts +31 -0
- package/lib/templates/NowPlayingTemplate.d.ts.map +1 -0
- package/lib/templates/PointOfInterestTemplate.d.ts +33 -0
- package/lib/templates/PointOfInterestTemplate.d.ts.map +1 -0
- package/lib/templates/SearchTemplate.d.ts +32 -0
- package/lib/templates/SearchTemplate.d.ts.map +1 -0
- package/lib/templates/SearchTemplate.js +2 -2
- package/lib/templates/TabBarTemplate.d.ts +27 -0
- package/lib/templates/TabBarTemplate.d.ts.map +1 -0
- package/lib/templates/Template.d.ts +82 -0
- package/lib/templates/Template.d.ts.map +1 -0
- package/lib/templates/VoiceControlTemplate.d.ts +18 -0
- package/lib/templates/VoiceControlTemplate.d.ts.map +1 -0
- package/lib/templates/android/AndroidNavigationBaseTemplate.d.ts +19 -0
- package/lib/templates/android/AndroidNavigationBaseTemplate.d.ts.map +1 -0
- package/lib/templates/android/MessageTemplate.d.ts +16 -0
- package/lib/templates/android/MessageTemplate.d.ts.map +1 -0
- package/lib/templates/android/NavigationTemplate.d.ts +44 -0
- package/lib/templates/android/NavigationTemplate.d.ts.map +1 -0
- package/lib/templates/android/PaneTemplate.d.ts +13 -0
- package/lib/templates/android/PaneTemplate.d.ts.map +1 -0
- package/lib/templates/android/PlaceListMapTemplate.d.ts +58 -0
- package/lib/templates/android/PlaceListMapTemplate.d.ts.map +1 -0
- package/lib/templates/android/PlaceListNavigationTemplate.d.ts +51 -0
- package/lib/templates/android/PlaceListNavigationTemplate.d.ts.map +1 -0
- package/lib/templates/android/RoutePreviewNavigationTemplate.d.ts +60 -0
- package/lib/templates/android/RoutePreviewNavigationTemplate.d.ts.map +1 -0
- package/package.json +3 -3
- package/react-native-carplay.podspec +3 -3
- package/src/CarPlay.ts +28 -16
- package/src/interfaces/ListItem.ts +14 -8
- package/src/interfaces/ListSection.ts +1 -1
- package/src/templates/ListTemplate.ts +64 -44
- package/src/templates/NowPlayingTemplate.ts +10 -3
- package/src/templates/SearchTemplate.ts +2 -2
- package/README.md +0 -633
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
package org.birkir.carplay
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import android.os.Handler
|
|
5
|
+
import android.os.Looper
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import androidx.activity.OnBackPressedCallback
|
|
8
|
+
import androidx.car.app.AppManager
|
|
9
|
+
import androidx.car.app.CarContext
|
|
10
|
+
import androidx.car.app.CarToast
|
|
11
|
+
import androidx.car.app.ScreenManager
|
|
12
|
+
import androidx.car.app.SessionInfo
|
|
13
|
+
import androidx.car.app.model.Alert
|
|
14
|
+
import androidx.car.app.model.AlertCallback
|
|
15
|
+
import androidx.car.app.model.CarText
|
|
16
|
+
import androidx.car.app.model.Distance
|
|
17
|
+
import androidx.car.app.model.TabTemplate
|
|
18
|
+
import androidx.car.app.model.Template
|
|
19
|
+
import com.facebook.react.bridge.Arguments
|
|
20
|
+
import com.facebook.react.bridge.Callback
|
|
21
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
22
|
+
import com.facebook.react.bridge.Promise
|
|
23
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
24
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
25
|
+
import com.facebook.react.bridge.ReactMethod
|
|
26
|
+
import com.facebook.react.bridge.ReadableMap
|
|
27
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
28
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
29
|
+
import com.facebook.react.modules.debug.DevSettingsModule
|
|
30
|
+
import org.birkir.carplay.parser.Parser
|
|
31
|
+
import org.birkir.carplay.parser.TemplateParser
|
|
32
|
+
import org.birkir.carplay.screens.CarScreen
|
|
33
|
+
import org.birkir.carplay.screens.CarScreenContext
|
|
34
|
+
import org.birkir.carplay.utils.EventEmitter
|
|
35
|
+
import java.util.WeakHashMap
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@ReactModule(name = CarPlayModule.NAME)
|
|
39
|
+
class CarPlayModule internal constructor(private val reactContext: ReactApplicationContext) :
|
|
40
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
41
|
+
|
|
42
|
+
private lateinit var carContext: CarContext
|
|
43
|
+
private lateinit var parser: Parser;
|
|
44
|
+
|
|
45
|
+
private var currentCarScreen: CarScreen? = null
|
|
46
|
+
private var screenManager: ScreenManager? = null
|
|
47
|
+
private val carScreens: WeakHashMap<String, CarScreen> = WeakHashMap()
|
|
48
|
+
private val carTemplates: WeakHashMap<String, ReadableMap> = WeakHashMap()
|
|
49
|
+
private val carScreenContexts: WeakHashMap<CarScreen, CarScreenContext> =
|
|
50
|
+
WeakHashMap()
|
|
51
|
+
private val handler: Handler = Handler(Looper.getMainLooper())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
// Global event emitter (no templateId's)
|
|
55
|
+
private var eventEmitter: EventEmitter? = null
|
|
56
|
+
|
|
57
|
+
init {
|
|
58
|
+
reactContext.addLifecycleEventListener(object : LifecycleEventListener {
|
|
59
|
+
override fun onHostResume() {
|
|
60
|
+
eventEmitter = EventEmitter(reactContext)
|
|
61
|
+
reactContext.getNativeModule(DevSettingsModule::class.java)
|
|
62
|
+
?.addMenuItem("Reload Android Auto")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override fun onHostPause() {}
|
|
66
|
+
override fun onHostDestroy() {}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override fun getName(): String {
|
|
71
|
+
return NAME
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fun setCarContext(carContext: CarContext, currentCarScreen: CarScreen) {
|
|
75
|
+
parser = Parser(carContext, CarScreenContext("", eventEmitter!!, carScreens));
|
|
76
|
+
this.carContext = carContext
|
|
77
|
+
this.currentCarScreen = currentCarScreen
|
|
78
|
+
screenManager = currentCarScreen.screenManager
|
|
79
|
+
carScreens["root"] = this.currentCarScreen
|
|
80
|
+
carContext.onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
|
|
81
|
+
override fun handleOnBackPressed() {
|
|
82
|
+
eventEmitter?.backButtonPressed(screenManager?.top?.marker)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
eventEmitter?.didConnect()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private fun parseTemplate(
|
|
89
|
+
config: ReadableMap,
|
|
90
|
+
carScreenContext: CarScreenContext
|
|
91
|
+
): Template {
|
|
92
|
+
val factory = TemplateParser(carContext, carScreenContext)
|
|
93
|
+
return factory.parse(config)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@ReactMethod
|
|
97
|
+
fun checkForConnection() {
|
|
98
|
+
eventEmitter?.didConnect()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@ReactMethod
|
|
102
|
+
fun createTemplate(templateId: String, config: ReadableMap, callback: Callback?) {
|
|
103
|
+
handler.post {
|
|
104
|
+
Log.d(TAG, "Creating template $templateId")
|
|
105
|
+
|
|
106
|
+
// Store the template
|
|
107
|
+
carTemplates[templateId] = config;
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
createScreen(templateId);
|
|
111
|
+
callback?.invoke()
|
|
112
|
+
} catch (err: IllegalArgumentException) {
|
|
113
|
+
val args = Arguments.createMap()
|
|
114
|
+
args.putString("error", "Failed to parse template '$templateId': ${err.message}")
|
|
115
|
+
callback?.invoke(args)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@ReactMethod
|
|
121
|
+
fun updateTemplate(templateId: String, config: ReadableMap) {
|
|
122
|
+
handler.post {
|
|
123
|
+
carTemplates[templateId] = config;
|
|
124
|
+
val screen = carScreens[templateId]
|
|
125
|
+
if (screen != null) {
|
|
126
|
+
val carScreenContext = carScreenContexts[screen];
|
|
127
|
+
if (carScreenContext != null) {
|
|
128
|
+
val template = parseTemplate(config, carScreenContext);
|
|
129
|
+
screen.setTemplate(template, templateId, config);
|
|
130
|
+
screen.invalidate()
|
|
131
|
+
// If this is a tab template, we need to update the main tab screen as well
|
|
132
|
+
if (template is TabTemplate) {
|
|
133
|
+
carScreens[carScreenContext.screenMarker]?.setTemplate(template, carScreenContext.screenMarker, config)
|
|
134
|
+
carScreens[carScreenContext.screenMarker]?.invalidate()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@ReactMethod
|
|
142
|
+
fun setRootTemplate(templateId: String, animated: Boolean?) {
|
|
143
|
+
Log.d(TAG, "set Root Template for $templateId")
|
|
144
|
+
handler.post {
|
|
145
|
+
val screen = getScreen(templateId)
|
|
146
|
+
if (screen != null) {
|
|
147
|
+
currentCarScreen = screen
|
|
148
|
+
screenManager?.popToRoot()
|
|
149
|
+
screenManager?.push(screen)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@ReactMethod
|
|
155
|
+
fun pushTemplate(templateId: String, animated: Boolean?) {
|
|
156
|
+
handler.post {
|
|
157
|
+
val screen = getScreen(templateId)
|
|
158
|
+
if (screen != null) {
|
|
159
|
+
currentCarScreen = screen;
|
|
160
|
+
screenManager?.push(screen)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@ReactMethod
|
|
166
|
+
fun popToTemplate(templateId: String, animated: Boolean?) {
|
|
167
|
+
handler.post {
|
|
168
|
+
screenManager?.popTo(templateId);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@ReactMethod
|
|
173
|
+
fun popTemplate(animated: Boolean?) {
|
|
174
|
+
handler.post {
|
|
175
|
+
screenManager!!.pop()
|
|
176
|
+
removeScreen(currentCarScreen)
|
|
177
|
+
currentCarScreen = screenManager!!.top as CarScreen
|
|
178
|
+
currentCarScreen?.invalidate()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@ReactMethod
|
|
183
|
+
fun presentTemplate(templateId: String?, animated: Boolean?) {
|
|
184
|
+
// void
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@ReactMethod
|
|
188
|
+
fun dismissTemplate(templateId: String?, animated: Boolean?) {
|
|
189
|
+
// void
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// pragma: Android Auto only stuff
|
|
193
|
+
|
|
194
|
+
@ReactMethod
|
|
195
|
+
fun toast(text: String, duration: Int) {
|
|
196
|
+
if (!::carContext.isInitialized) {
|
|
197
|
+
Log.e(TAG, "carContext is not initialized. Cannot show toast.")
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
CarToast.makeText(carContext, text, duration).show()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@ReactMethod
|
|
204
|
+
fun alert(props: ReadableMap) {
|
|
205
|
+
handler.post {
|
|
206
|
+
val id = props.getInt("id");
|
|
207
|
+
val title = parser.parseCarText(props.getString("title")!!, props);
|
|
208
|
+
val duration = props.getInt("duration").toLong();
|
|
209
|
+
val alert = Alert.Builder(id, title, duration).apply {
|
|
210
|
+
setCallback(object : AlertCallback {
|
|
211
|
+
override fun onCancel(reason: Int) {
|
|
212
|
+
val reasonString = when (reason) {
|
|
213
|
+
AlertCallback.REASON_TIMEOUT -> "timeout"
|
|
214
|
+
AlertCallback.REASON_USER_ACTION -> "userAction"
|
|
215
|
+
AlertCallback.REASON_NOT_SUPPORTED -> "notSupported"
|
|
216
|
+
else -> "unknown"
|
|
217
|
+
}
|
|
218
|
+
eventEmitter?.alertActionPressed("cancel", reasonString);
|
|
219
|
+
}
|
|
220
|
+
override fun onDismiss() {
|
|
221
|
+
eventEmitter?.alertActionPressed("dismiss" );
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
props.getString("subtitle")?.let { setSubtitle(parser.parseCarText(it, props)) }
|
|
225
|
+
props.getMap("icon")?.let { setIcon(parser.parseCarIcon(it)) }
|
|
226
|
+
props.getArray("actions")?.let {
|
|
227
|
+
for (i in 0 until it.size()) {
|
|
228
|
+
addAction(parser.parseAction(it.getMap(i)));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}.build()
|
|
232
|
+
carContext.getCarService(AppManager::class.java).showAlert(alert)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@ReactMethod
|
|
237
|
+
fun dismissAlert(alertId: Int) {
|
|
238
|
+
carContext.getCarService(AppManager::class.java).dismissAlert(alertId)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
@ReactMethod
|
|
242
|
+
fun invalidate(templateId: String) {
|
|
243
|
+
handler.post {
|
|
244
|
+
val screen = getScreen(templateId)
|
|
245
|
+
if (screen === screenManager!!.top) {
|
|
246
|
+
Log.d(TAG, "Invalidated screen $templateId")
|
|
247
|
+
screen.invalidate()
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@ReactMethod
|
|
253
|
+
fun reload() {
|
|
254
|
+
val intent = Intent("org.birkir.carplay.APP_RELOAD")
|
|
255
|
+
reactContext.sendBroadcast(intent)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@ReactMethod
|
|
259
|
+
fun getHostInfo(promise: Promise) {
|
|
260
|
+
return promise.resolve(Arguments.createMap().apply {
|
|
261
|
+
carContext.hostInfo?.packageName?.let { putString("packageName", it) }
|
|
262
|
+
carContext.hostInfo?.uid?.let { putInt("uid", it) }
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Others
|
|
267
|
+
|
|
268
|
+
@ReactMethod
|
|
269
|
+
fun addListener(eventName: String) {
|
|
270
|
+
Log.d(TAG, "listener added $eventName")
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@ReactMethod
|
|
274
|
+
fun removeListeners(count: Int) {
|
|
275
|
+
Log.d(TAG, "remove listeners $count")
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private fun createCarScreenContext(screen: CarScreen): CarScreenContext {
|
|
279
|
+
val templateId = screen.marker!!
|
|
280
|
+
return CarScreenContext(templateId, EventEmitter(reactContext, templateId), carScreens)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private fun createScreen(templateId: String): CarScreen? {
|
|
284
|
+
if (!::carContext.isInitialized) {
|
|
285
|
+
Log.e(TAG, "carContext is not initialized.")
|
|
286
|
+
return null
|
|
287
|
+
}
|
|
288
|
+
val config = carTemplates[templateId];
|
|
289
|
+
if (config != null) {
|
|
290
|
+
val screen = CarScreen(carContext)
|
|
291
|
+
screen.marker = templateId;
|
|
292
|
+
|
|
293
|
+
// context
|
|
294
|
+
carScreenContexts.remove(screen)
|
|
295
|
+
val carScreenContext = createCarScreenContext(screen)
|
|
296
|
+
carScreenContexts[screen] = carScreenContext
|
|
297
|
+
|
|
298
|
+
val template = parseTemplate(config, carScreenContext);
|
|
299
|
+
screen.setTemplate(template, templateId, config)
|
|
300
|
+
carScreens[templateId] = screen;
|
|
301
|
+
|
|
302
|
+
return screen;
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private fun getScreen(name: String): CarScreen? {
|
|
308
|
+
return carScreens[name] ?: createScreen(name);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private fun removeScreen(screen: CarScreen?) {
|
|
312
|
+
val params = WritableNativeMap()
|
|
313
|
+
params.putString("screen", screen!!.marker)
|
|
314
|
+
carScreens.values.remove(screen)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
companion object {
|
|
318
|
+
const val NAME = "RNCarPlay"
|
|
319
|
+
const val TAG = "CarPlay"
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package org.birkir.carplay
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class CarPlayPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
val modules: MutableList<NativeModule> = ArrayList()
|
|
11
|
+
modules.add(CarPlayModule(reactContext))
|
|
12
|
+
return modules
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
16
|
+
return emptyList()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package org.birkir.carplay
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.content.IntentFilter
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import androidx.car.app.CarAppService
|
|
9
|
+
import androidx.car.app.Session
|
|
10
|
+
import androidx.car.app.SessionInfo
|
|
11
|
+
import androidx.car.app.validation.HostValidator
|
|
12
|
+
import com.facebook.react.ReactApplication
|
|
13
|
+
import com.facebook.react.ReactInstanceManager
|
|
14
|
+
|
|
15
|
+
class CarPlayService : CarAppService() {
|
|
16
|
+
private lateinit var reactInstanceManager: ReactInstanceManager
|
|
17
|
+
override fun onCreate() {
|
|
18
|
+
super.onCreate()
|
|
19
|
+
reactInstanceManager =
|
|
20
|
+
(application as ReactApplication).reactNativeHost.reactInstanceManager
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override fun createHostValidator(): HostValidator {
|
|
24
|
+
return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override fun onCreateSession(sessionInfo: SessionInfo): Session {
|
|
28
|
+
Log.d(TAG, "onCreateSession: sessionId = ${sessionInfo.sessionId}, display = ${sessionInfo.displayType}")
|
|
29
|
+
return CarPlaySession(reactInstanceManager)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
companion object {
|
|
33
|
+
var TAG = "CarPlayService"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
package org.birkir.carplay
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.BroadcastReceiver
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.content.Intent
|
|
7
|
+
import android.content.IntentFilter
|
|
8
|
+
import android.content.res.Configuration
|
|
9
|
+
import android.os.Build
|
|
10
|
+
import android.os.Bundle
|
|
11
|
+
import android.util.Log
|
|
12
|
+
import androidx.car.app.CarContext.SCREEN_SERVICE
|
|
13
|
+
import androidx.car.app.Screen
|
|
14
|
+
import androidx.car.app.ScreenManager
|
|
15
|
+
import androidx.car.app.Session
|
|
16
|
+
import androidx.lifecycle.DefaultLifecycleObserver
|
|
17
|
+
import androidx.lifecycle.LifecycleOwner
|
|
18
|
+
import com.facebook.react.ReactInstanceManager
|
|
19
|
+
import com.facebook.react.bridge.Arguments
|
|
20
|
+
import com.facebook.react.bridge.ReactContext
|
|
21
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
22
|
+
import com.facebook.react.modules.appregistry.AppRegistry
|
|
23
|
+
import com.facebook.react.modules.core.TimingModule
|
|
24
|
+
import com.facebook.react.modules.debug.DevSettingsModule
|
|
25
|
+
import org.birkir.carplay.screens.CarScreen
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CarPlaySession(private val reactInstanceManager: ReactInstanceManager) : Session(), DefaultLifecycleObserver {
|
|
29
|
+
private lateinit var screen: CarScreen
|
|
30
|
+
|
|
31
|
+
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
|
32
|
+
override fun onCreateScreen(intent: Intent): Screen {
|
|
33
|
+
Log.d(TAG, "On create screen " + intent.action + " - " + intent.dataString)
|
|
34
|
+
val lifecycle = lifecycle
|
|
35
|
+
lifecycle.addObserver(this)
|
|
36
|
+
screen = CarScreen(carContext)
|
|
37
|
+
screen.marker = "root"
|
|
38
|
+
|
|
39
|
+
// Handle reload events
|
|
40
|
+
if (Build.VERSION.SDK_INT >= 34 && carContext.getApplicationInfo().targetSdkVersion >= 34) {
|
|
41
|
+
carContext.registerReceiver(object : BroadcastReceiver() {
|
|
42
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
43
|
+
if ("org.birkir.carplay.APP_RELOAD" == intent.action) {
|
|
44
|
+
invokeStartTask(reactInstanceManager.currentReactContext!!);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}, IntentFilter("org.birkir.carplay.APP_RELOAD"), Context.RECEIVER_EXPORTED)
|
|
48
|
+
}else{
|
|
49
|
+
carContext.registerReceiver(object : BroadcastReceiver() {
|
|
50
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
51
|
+
if ("org.birkir.carplay.APP_RELOAD" == intent.action) {
|
|
52
|
+
invokeStartTask(reactInstanceManager.currentReactContext!!);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}, IntentFilter("org.birkir.carplay.APP_RELOAD"))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Run JS
|
|
59
|
+
runJsApplication()
|
|
60
|
+
return screen
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private fun runJsApplication() {
|
|
64
|
+
val reactContext = reactInstanceManager.currentReactContext
|
|
65
|
+
if (reactContext == null) {
|
|
66
|
+
reactInstanceManager.addReactInstanceEventListener(
|
|
67
|
+
object : ReactInstanceManager.ReactInstanceEventListener {
|
|
68
|
+
override fun onReactContextInitialized(reactContext: ReactContext) {
|
|
69
|
+
invokeStartTask(reactContext)
|
|
70
|
+
reactInstanceManager.removeReactInstanceEventListener(this)
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
reactInstanceManager.createReactContextInBackground()
|
|
74
|
+
} else {
|
|
75
|
+
invokeStartTask(reactContext)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private fun invokeStartTask(reactContext: ReactContext) {
|
|
80
|
+
try {
|
|
81
|
+
val catalystInstance = reactContext.catalystInstance
|
|
82
|
+
val jsAppModuleName = "AndroidAuto"
|
|
83
|
+
val appParams = WritableNativeMap()
|
|
84
|
+
appParams.putDouble("rootTag", 1.0)
|
|
85
|
+
val appProperties = Bundle.EMPTY
|
|
86
|
+
if (appProperties != null) {
|
|
87
|
+
appParams.putMap("initialProps", Arguments.fromBundle(appProperties))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
catalystInstance.getJSModule(AppRegistry::class.java)
|
|
91
|
+
.runApplication(jsAppModuleName, appParams)
|
|
92
|
+
|
|
93
|
+
val timingModule = reactContext.getNativeModule(
|
|
94
|
+
TimingModule::class.java
|
|
95
|
+
)
|
|
96
|
+
val carModule = reactInstanceManager
|
|
97
|
+
.currentReactContext?.getNativeModule(CarPlayModule::class.java)
|
|
98
|
+
carModule!!.setCarContext(carContext, screen)
|
|
99
|
+
timingModule!!.onHostResume()
|
|
100
|
+
|
|
101
|
+
} catch (e: Exception) {
|
|
102
|
+
e.printStackTrace()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
override fun onDestroy(owner: LifecycleOwner) {
|
|
107
|
+
Log.i(TAG, "onDestroy")
|
|
108
|
+
val context = carContext
|
|
109
|
+
// stop services here, if any
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
override fun onNewIntent(intent: Intent) {
|
|
113
|
+
// handle intents
|
|
114
|
+
Log.d(TAG, "CarPlaySession.onNewIntent")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override fun onCarConfigurationChanged(configuration: Configuration) {
|
|
118
|
+
// we should report this over the bridge
|
|
119
|
+
Log.d(TAG, "CarPlaySession.onCarConfigurationChanged")
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
companion object {
|
|
123
|
+
const val TAG = "CarPlaySession"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package org.birkir.carplay.parser
|
|
2
|
+
|
|
3
|
+
import androidx.car.app.CarContext
|
|
4
|
+
import androidx.car.app.model.Pane
|
|
5
|
+
import androidx.car.app.model.PaneTemplate
|
|
6
|
+
import androidx.car.app.model.Template
|
|
7
|
+
import com.facebook.react.bridge.ReadableMap
|
|
8
|
+
import org.birkir.carplay.screens.CarScreenContext
|
|
9
|
+
|
|
10
|
+
class Parser(
|
|
11
|
+
context: CarContext,
|
|
12
|
+
carScreenContext: CarScreenContext
|
|
13
|
+
) : RCTTemplate(context, carScreenContext) {
|
|
14
|
+
override fun parse(props: ReadableMap): Template {
|
|
15
|
+
return PaneTemplate.Builder(Pane.Builder().build()).build()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package org.birkir.carplay.parser
|
|
2
|
+
|
|
3
|
+
import androidx.car.app.CarContext
|
|
4
|
+
import androidx.car.app.model.GridTemplate
|
|
5
|
+
import com.facebook.react.bridge.ReadableMap
|
|
6
|
+
import org.birkir.carplay.screens.CarScreenContext
|
|
7
|
+
|
|
8
|
+
class RCTGridTemplate(
|
|
9
|
+
context: CarContext,
|
|
10
|
+
carScreenContext: CarScreenContext
|
|
11
|
+
) : RCTTemplate(context, carScreenContext) {
|
|
12
|
+
|
|
13
|
+
override fun parse(props: ReadableMap): GridTemplate {
|
|
14
|
+
return GridTemplate.Builder().apply {
|
|
15
|
+
setLoading(props.isLoading())
|
|
16
|
+
props.getString("title")?.let { setTitle(it) }
|
|
17
|
+
props.getMap("headerAction")?.let { setHeaderAction(parseAction(it)) }
|
|
18
|
+
props.getArray("actions")?.let { setActionStrip(parseActionStrip(it)) }
|
|
19
|
+
this.setSingleList(
|
|
20
|
+
parseItemList(props.getArray("buttons"), "grid")
|
|
21
|
+
)
|
|
22
|
+
}.build()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
companion object {
|
|
26
|
+
const val TAG = "RCTGridTemplate"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package org.birkir.carplay.parser
|
|
2
|
+
|
|
3
|
+
import androidx.car.app.CarContext
|
|
4
|
+
import androidx.car.app.model.ListTemplate
|
|
5
|
+
import androidx.car.app.model.SectionedItemList
|
|
6
|
+
import com.facebook.react.bridge.ReadableMap
|
|
7
|
+
import org.birkir.carplay.screens.CarScreenContext
|
|
8
|
+
|
|
9
|
+
class RCTListTemplate(
|
|
10
|
+
context: CarContext,
|
|
11
|
+
screenContext: CarScreenContext
|
|
12
|
+
) : RCTTemplate(context, screenContext) {
|
|
13
|
+
|
|
14
|
+
override fun parse(props: ReadableMap): ListTemplate {
|
|
15
|
+
return ListTemplate.Builder().apply {
|
|
16
|
+
props.getString("title")?.let { setTitle(it) }
|
|
17
|
+
|
|
18
|
+
// Actions
|
|
19
|
+
props.getArray("actions")?.let {
|
|
20
|
+
setActionStrip(
|
|
21
|
+
parseActionStrip(it)
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Header Action
|
|
26
|
+
props.getMap("headerAction")?.let {
|
|
27
|
+
setHeaderAction(
|
|
28
|
+
parseAction(it)
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Loading
|
|
33
|
+
setLoading(props.isLoading())
|
|
34
|
+
|
|
35
|
+
// Sections
|
|
36
|
+
props.getArray("sections")?.let {
|
|
37
|
+
for (i in 0 until it.size()) {
|
|
38
|
+
val section = it.getMap(i)
|
|
39
|
+
val header = section.getString("header")
|
|
40
|
+
addSectionedList(
|
|
41
|
+
SectionedItemList.create(
|
|
42
|
+
parseItemList(section.getArray("items")),
|
|
43
|
+
header ?: "Missing title"
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Single List
|
|
50
|
+
// @todo handle when sections and items are defined at once.
|
|
51
|
+
props.getArray("items")?.let {
|
|
52
|
+
setSingleList(
|
|
53
|
+
parseItemList(it)
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}.build()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
companion object {
|
|
62
|
+
const val TAG = "RCTListTemplate"
|
|
63
|
+
}
|
|
64
|
+
}
|