From 51fc1effc3723cb508ad32f28257d6ea60f000e9 Mon Sep 17 00:00:00 2001 From: Jan Kleinert Date: Fri, 5 Jun 2026 18:56:43 +0000 Subject: [PATCH 1/8] Migrate another set of Cars snippets --- cars/build.gradle.kts | 1 + cars/src/main/AndroidManifest.xml | 1 + .../java/com/example/cars/apps/Navigation.kt | 309 ++++++++++++++++++ .../cars/apps/library/CarAppServiceSession.kt | 54 +++ .../example/cars/apps/library/CarHardware.kt | 66 ++++ .../cars/apps/library/CarMicrophone.kt | 84 +++++ .../cars/apps/library/ConnectionApi.kt | 53 +++ .../example/cars/apps/library/Constraints.kt | 33 ++ .../cars/apps/library/InlineCarIcons.kt | 55 ++++ .../cars/apps/library/ScreenNavigation.kt | 52 +++ .../cars/apps/library/TextStringVariants.kt | 40 +++ .../cars/communication/TemplatedMessaging.kt | 111 +++++++ .../com/example/cars/parked/ParkedAaos.kt | 56 ++++ .../com/example/cars/parked/ParkedAuto.kt | 34 ++ .../java/com/example/cars/parked/VideoApp.kt | 31 ++ .../src/main/res/drawable/ic_mark_as_read.xml | 21 ++ .../src/main/res/drawable/ic_notification.xml | 21 ++ cars/src/main/res/drawable/ic_reply.xml | 21 ++ cars/src/main/res/drawable/ic_star.xml | 21 ++ 19 files changed, 1064 insertions(+) create mode 100644 cars/src/main/java/com/example/cars/apps/Navigation.kt create mode 100644 cars/src/main/java/com/example/cars/apps/library/CarAppServiceSession.kt create mode 100644 cars/src/main/java/com/example/cars/apps/library/CarHardware.kt create mode 100644 cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt create mode 100644 cars/src/main/java/com/example/cars/apps/library/ConnectionApi.kt create mode 100644 cars/src/main/java/com/example/cars/apps/library/Constraints.kt create mode 100644 cars/src/main/java/com/example/cars/apps/library/InlineCarIcons.kt create mode 100644 cars/src/main/java/com/example/cars/apps/library/ScreenNavigation.kt create mode 100644 cars/src/main/java/com/example/cars/apps/library/TextStringVariants.kt create mode 100644 cars/src/main/java/com/example/cars/communication/TemplatedMessaging.kt create mode 100644 cars/src/main/java/com/example/cars/parked/ParkedAaos.kt create mode 100644 cars/src/main/java/com/example/cars/parked/ParkedAuto.kt create mode 100644 cars/src/main/java/com/example/cars/parked/VideoApp.kt create mode 100644 cars/src/main/res/drawable/ic_mark_as_read.xml create mode 100644 cars/src/main/res/drawable/ic_notification.xml create mode 100644 cars/src/main/res/drawable/ic_reply.xml create mode 100644 cars/src/main/res/drawable/ic_star.xml diff --git a/cars/build.gradle.kts b/cars/build.gradle.kts index 0fb7ec7d4..74cd8b44d 100644 --- a/cars/build.gradle.kts +++ b/cars/build.gradle.kts @@ -29,6 +29,7 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + useLibrary("android.car") } dependencies { diff --git a/cars/src/main/AndroidManifest.xml b/cars/src/main/AndroidManifest.xml index 0d77fa244..58ca61aa7 100644 --- a/cars/src/main/AndroidManifest.xml +++ b/cars/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ --> + { data -> + if (data.rangeRemainingMeters.status == CarValue.STATUS_SUCCESS) { + val rangeRemaining = data.rangeRemainingMeters.value + } else { + // Handle error + } + } + + carInfo.addEnergyLevelListener(carContext.mainExecutor, listener) + // ... + // Unregister the listener when you no longer need updates + carInfo.removeEnergyLevelListener(listener) + // [END android_cars_apps_library_car_hardware_info] + } + + fun demoCarSensors() { + // [START android_cars_apps_library_car_hardware_sensors] + val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors + + val listener = OnCarDataAvailableListener { data -> + if (data.orientations.status == CarValue.STATUS_SUCCESS) { + val orientation = data.orientations.value + } else { + // Data not available, handle error + } + } + + carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, carContext.mainExecutor, listener) + // ... + // Unregister the listener when you no longer need updates + carSensors.removeCompassListener(listener) + // [END android_cars_apps_library_car_hardware_sensors] + } +} diff --git a/cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt b/cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt new file mode 100644 index 000000000..f8aa65425 --- /dev/null +++ b/cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cars.apps.library + +import android.Manifest +import android.media.AudioAttributes +import android.media.AudioFocusRequest +import android.media.AudioManager +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.annotation.RequiresPermission +import androidx.car.app.CarContext +import androidx.car.app.annotations.RequiresCarApi +import androidx.car.app.media.CarAudioRecord + +@RequiresCarApi(5) +class CarMicrophone(private val carContext: CarContext) { + + @RequiresPermission(Manifest.permission.RECORD_AUDIO) + fun recordAudio() { + // [START android_cars_apps_library_car_microphone_record] + val carAudioRecord = CarAudioRecord.create(carContext) + carAudioRecord.startRecording() + + val data = ByteArray(CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) + while (carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) { + // Use data array + // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech + } + carAudioRecord.stopRecording() + // [END android_cars_apps_library_car_microphone_record] + } + + @RequiresPermission(Manifest.permission.RECORD_AUDIO) + @RequiresApi(Build.VERSION_CODES.O) + fun acquireAudioFocusAndRecord() { + // [START android_cars_apps_library_car_microphone_audio_focus] + val carAudioRecord = CarAudioRecord.create(carContext) + + // Take audio focus so that user's media is not recorded + val audioAttributes = AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + // Use the most appropriate usage type for your use case + .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE) + .build() + + val audioFocusRequest = + AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) + .setAudioAttributes(audioAttributes) + .setOnAudioFocusChangeListener { state: Int -> + if (state == AudioManager.AUDIOFOCUS_LOSS) { + // Stop recording if audio focus is lost + carAudioRecord.stopRecording() + } + } + .build() + + if (carContext.getSystemService(AudioManager::class.java) + .requestAudioFocus(audioFocusRequest) + != AudioManager.AUDIOFOCUS_REQUEST_GRANTED + ) { + // Don't record if the focus isn't granted + return + } + + carAudioRecord.startRecording() + // Process the audio and abandon the AudioFocusRequest when done + // [END android_cars_apps_library_car_microphone_audio_focus] + } +} diff --git a/cars/src/main/java/com/example/cars/apps/library/ConnectionApi.kt b/cars/src/main/java/com/example/cars/apps/library/ConnectionApi.kt new file mode 100644 index 000000000..8d6c29ffc --- /dev/null +++ b/cars/src/main/java/com/example/cars/apps/library/ConnectionApi.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cars.apps.library + +import android.content.Intent +import androidx.car.app.CarContext +import androidx.car.app.CarToast +import androidx.car.app.Screen +import androidx.car.app.Session +import androidx.car.app.connection.CarConnection +import androidx.car.app.model.MessageTemplate +import androidx.car.app.model.Template + +class MyConnectionSession(carContext: CarContext) : Session() { + override fun onCreateScreen(intent: Intent): Screen { + // [START android_cars_apps_library_connection_api_observe] + CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated) + // [END android_cars_apps_library_connection_api_observe] + return MyConnectionScreen(carContext) + } + + // [START android_cars_apps_library_connection_api_handler] + fun onConnectionStateUpdated(connectionState: Int) { + val message = when (connectionState) { + CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit" + CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS" + CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto" + else -> "Unknown car connection type" + } + CarToast.makeText(carContext, message, CarToast.LENGTH_SHORT).show() + } + // [END android_cars_apps_library_connection_api_handler] +} + +class MyConnectionScreen(carContext: CarContext) : Screen(carContext) { + override fun onGetTemplate(): Template { + return MessageTemplate.Builder("Connection API Demo").build() + } +} diff --git a/cars/src/main/java/com/example/cars/apps/library/Constraints.kt b/cars/src/main/java/com/example/cars/apps/library/Constraints.kt new file mode 100644 index 000000000..b96f71641 --- /dev/null +++ b/cars/src/main/java/com/example/cars/apps/library/Constraints.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cars.apps.library + +import androidx.car.app.CarContext +import androidx.car.app.constraints.ConstraintManager + +class Constraints(private val carContext: CarContext) { + + fun setupAndQueryLimit() { + // [START android_cars_apps_library_constraints_api_get_contstraint_manager] + val manager = carContext.getCarService(ConstraintManager::class.java) + // [END android_cars_apps_library_constraints_api_get_contstraint_manager] + + // [START android_cars_apps_library_constraints_api_limit] + val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID) + // [END android_cars_apps_library_constraints_api_limit] + } +} diff --git a/cars/src/main/java/com/example/cars/apps/library/InlineCarIcons.kt b/cars/src/main/java/com/example/cars/apps/library/InlineCarIcons.kt new file mode 100644 index 000000000..d8768583e --- /dev/null +++ b/cars/src/main/java/com/example/cars/apps/library/InlineCarIcons.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cars.apps.library + +import android.text.SpannableString +import android.text.Spanned +import androidx.car.app.CarContext +import androidx.car.app.model.CarIcon +import androidx.car.app.model.CarIconSpan +import androidx.car.app.model.Row +import androidx.core.graphics.drawable.IconCompat +import com.example.cars.R + +class InlineCarIcons(private val carContext: CarContext) { + + fun addInlineIcon() { + // [START android_cars_apps_library_inline_car_icons] + val rating = SpannableString("Rating: 4.5 stars") + rating.setSpan( + CarIconSpan.create( + // Create a CarIcon with an image of four and a half stars + CarIcon.Builder( + IconCompat.createWithResource(carContext, R.drawable.ic_star) + ).build(), + // Align the CarIcon to the baseline of the text + CarIconSpan.ALIGN_BASELINE + ), + // The start index of the span (index of the character '4') + 8, + // The end index of the span (index of the last 's' in "stars") + 16, + Spanned.SPAN_INCLUSIVE_INCLUSIVE + ) + + val row = Row.Builder() + .setTitle("Rating Row") + .addText(rating) + .build() + // [END android_cars_apps_library_inline_car_icons] + } +} diff --git a/cars/src/main/java/com/example/cars/apps/library/ScreenNavigation.kt b/cars/src/main/java/com/example/cars/apps/library/ScreenNavigation.kt new file mode 100644 index 000000000..a6b97ff1b --- /dev/null +++ b/cars/src/main/java/com/example/cars/apps/library/ScreenNavigation.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cars.apps.library + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.Header +import androidx.car.app.model.MessageTemplate +import androidx.car.app.model.Template + +class MyNavigationScreen(carContext: CarContext) : Screen(carContext) { + + override fun onGetTemplate(): Template { + // [START android_cars_apps_library_screen_navigation] + val header = Header.Builder() + .setStartHeaderAction(Action.BACK) + .build() + + val template = MessageTemplate.Builder("Hello world!") + .setHeader(header) + .addAction( + Action.Builder() + .setTitle("Next screen") + .setOnClickListener { screenManager.push(NextScreen(carContext)) } + .build() + ) + .build() + // [END android_cars_apps_library_screen_navigation] + return template + } +} + +class NextScreen(carContext: CarContext) : Screen(carContext) { + override fun onGetTemplate(): Template { + return MessageTemplate.Builder("Next Screen").build() + } +} diff --git a/cars/src/main/java/com/example/cars/apps/library/TextStringVariants.kt b/cars/src/main/java/com/example/cars/apps/library/TextStringVariants.kt new file mode 100644 index 000000000..370d9d48f --- /dev/null +++ b/cars/src/main/java/com/example/cars/apps/library/TextStringVariants.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cars.apps.library + +import androidx.car.app.CarContext +import androidx.car.app.model.CarIcon +import androidx.car.app.model.CarText +import androidx.car.app.model.GridItem + +class TextStringVariants(private val carContext: CarContext) { + + fun addVariants() { + // [START android_cars_apps_library_text_string_variants_builder] + val itemTitle = CarText.Builder("This is a very long string") + .addVariant("Shorter string") + .build() + // [END android_cars_apps_library_text_string_variants_builder] + + // [START android_cars_apps_library_text_string_variants_grid_item] + val gridItem = GridItem.Builder() + .setTitle(itemTitle) + .setImage(CarIcon.APP_ICON) + .build() + // [END android_cars_apps_library_text_string_variants_grid_item] + } +} diff --git a/cars/src/main/java/com/example/cars/communication/TemplatedMessaging.kt b/cars/src/main/java/com/example/cars/communication/TemplatedMessaging.kt new file mode 100644 index 000000000..71c4a7feb --- /dev/null +++ b/cars/src/main/java/com/example/cars/communication/TemplatedMessaging.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cars.communication + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.messaging.model.CarMessage +import androidx.car.app.messaging.model.ConversationCallback +import androidx.car.app.messaging.model.ConversationItem +import androidx.car.app.model.Action +import androidx.car.app.model.CarIcon +import androidx.car.app.model.CarText +import androidx.car.app.model.Header +import androidx.car.app.model.ItemList +import androidx.car.app.model.ListTemplate +import androidx.car.app.model.Template +import androidx.core.app.Person +import androidx.core.graphics.drawable.IconCompat +import com.example.cars.R + +class MyMessage( + val sender: Person, + val body: CarText, + val receivedTimeEpochMillis: Long, + val isRead: Boolean +) + +class MyConversation(val id: String, val title: String) { + fun getMessages(): List = emptyList() +} + +// [START android_cars_communication_templated_messaging_screen] +class MyMessagingScreen(carContext: CarContext) : Screen(carContext) { + + override fun onGetTemplate(): Template { + val itemListBuilder = ItemList.Builder() + val conversations: List = emptyList() // Retrieve conversations + + for (conversation: MyConversation in conversations) { + val carMessages: List = conversation.getMessages() + .map { message -> + // CarMessage supports additional fields such as MIME type and URI, + // which you should set if available + CarMessage.Builder() + .setSender(message.sender) + .setBody(message.body) + .setReceivedTimeEpochMillis(message.receivedTimeEpochMillis) + .setRead(message.isRead) + .build() + } + + itemListBuilder.addItem( + ConversationItem.Builder() + .setConversationCallback(object : ConversationCallback { + override fun onMarkAsRead() { + // Implement mark as read logic + } + + override fun onTextReply(replyText: String) { + // Implement text reply logic + } + }) + .setId(conversation.id) + .setTitle(CarText.create(conversation.title)) + .setIcon( + CarIcon.Builder( + IconCompat.createWithResource(carContext, R.drawable.ic_garage) + ).build() + ) + .setMessages(carMessages) + /* When the sender of a CarMessage is equal to this Person, + message readout is adjusted to "you said" instead of " + said" */ + .setSelf( + Person.Builder() + .setName("Me") + .setKey("self_id") + .build() + ) + // Set to true if the message contains more than 2 participants + .setGroupConversation(false) + .build() + ) + } + + val header = Header.Builder() + .setStartHeaderAction(Action.APP_ICON) + .setTitle("Conversations") + .build() + + return ListTemplate.Builder() + .setHeader(header) + .setSingleList(itemListBuilder.build()) + .build() + } +} +// [END android_cars_communication_templated_messaging_screen] diff --git a/cars/src/main/java/com/example/cars/parked/ParkedAaos.kt b/cars/src/main/java/com/example/cars/parked/ParkedAaos.kt new file mode 100644 index 000000000..ed6b6ad84 --- /dev/null +++ b/cars/src/main/java/com/example/cars/parked/ParkedAaos.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cars.parked + +import android.car.Car +import android.car.drivingstate.CarUxRestrictions +import android.car.drivingstate.CarUxRestrictionsManager +import android.content.Context +import android.content.pm.PackageManager + +class ParkedAaos(private val context: Context) { + + fun checkUxRestrictions() { + // [START android_cars_parked_automotive_os_ux_restrictions] + val car = Car.createCar(context) + val carUxRestrictionsManager = + car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager + + // You can either read the state directly ... + val currentUxRestrictions = carUxRestrictionsManager.currentCarUxRestrictions + + // or listen to state changes + carUxRestrictionsManager.registerListener { carUxRestrictions: CarUxRestrictions -> + // Handle UX restrictions + } + + // Don't forget to teardown and release resources when they're no longer needed + carUxRestrictionsManager.unregisterListener() + car.disconnect() + // [END android_cars_parked_automotive_os_ux_restrictions] + } + + fun detectAaos() { + val packageManager: PackageManager = context.packageManager + // [START android_cars_parked_automotive_os_detect] + val isCar = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + if (isCar) { + // Enable or disable a given feature + } + // [END android_cars_parked_automotive_os_detect] + } +} diff --git a/cars/src/main/java/com/example/cars/parked/ParkedAuto.kt b/cars/src/main/java/com/example/cars/parked/ParkedAuto.kt new file mode 100644 index 000000000..a9e1a0112 --- /dev/null +++ b/cars/src/main/java/com/example/cars/parked/ParkedAuto.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cars.parked + +import android.content.Context +import android.os.Build +import android.view.Display.DEFAULT_DISPLAY +import androidx.annotation.RequiresApi +import androidx.car.app.connection.CarConnection.CONNECTION_TYPE_PROJECTION + +class ParkedAuto { + @RequiresApi(Build.VERSION_CODES.R) + fun checkAndroidAuto(context: Context, connectionType: Int) { + val isRunningOnAndroidAuto: Boolean + // [START android_cars_parked_auto_detect] + val displayId = context.display.displayId + isRunningOnAndroidAuto = (connectionType == CONNECTION_TYPE_PROJECTION) && (displayId != DEFAULT_DISPLAY) + // [END android_cars_parked_auto_detect] + } +} diff --git a/cars/src/main/java/com/example/cars/parked/VideoApp.kt b/cars/src/main/java/com/example/cars/parked/VideoApp.kt new file mode 100644 index 000000000..df3dd960c --- /dev/null +++ b/cars/src/main/java/com/example/cars/parked/VideoApp.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cars.parked + +import android.content.Context +import androidx.car.app.features.CarFeatures + +class VideoApp { + fun checkBackgroundAudio(context: Context) { + // [START android_cars_parked_video_determine_support] + CarFeatures.isFeatureEnabled( + context, + CarFeatures.FEATURE_BACKGROUND_AUDIO_WHILE_DRIVING + ) + // [END android_cars_parked_video_determine_support] + } +} diff --git a/cars/src/main/res/drawable/ic_mark_as_read.xml b/cars/src/main/res/drawable/ic_mark_as_read.xml new file mode 100644 index 000000000..4744ee3ef --- /dev/null +++ b/cars/src/main/res/drawable/ic_mark_as_read.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/cars/src/main/res/drawable/ic_notification.xml b/cars/src/main/res/drawable/ic_notification.xml new file mode 100644 index 000000000..4744ee3ef --- /dev/null +++ b/cars/src/main/res/drawable/ic_notification.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/cars/src/main/res/drawable/ic_reply.xml b/cars/src/main/res/drawable/ic_reply.xml new file mode 100644 index 000000000..4744ee3ef --- /dev/null +++ b/cars/src/main/res/drawable/ic_reply.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/cars/src/main/res/drawable/ic_star.xml b/cars/src/main/res/drawable/ic_star.xml new file mode 100644 index 000000000..9f3b138b3 --- /dev/null +++ b/cars/src/main/res/drawable/ic_star.xml @@ -0,0 +1,21 @@ + + + + + From 95db0e6a5cd3726590b3d8824a33a70ee4321923 Mon Sep 17 00:00:00 2001 From: Jan Kleinert Date: Fri, 5 Jun 2026 19:49:24 +0000 Subject: [PATCH 2/8] Migrate another set of Cars snippets --- .../example/cars/apps/library/CarHardware.kt | 4 ++ .../cars/communication/TemplatedMessaging.kt | 56 ++++++++----------- 2 files changed, 27 insertions(+), 33 deletions(-) diff --git a/cars/src/main/java/com/example/cars/apps/library/CarHardware.kt b/cars/src/main/java/com/example/cars/apps/library/CarHardware.kt index 0b95e4bd1..faf319c89 100644 --- a/cars/src/main/java/com/example/cars/apps/library/CarHardware.kt +++ b/cars/src/main/java/com/example/cars/apps/library/CarHardware.kt @@ -16,6 +16,8 @@ package com.example.cars.apps.library +import android.os.Build +import androidx.annotation.RequiresApi import androidx.car.app.CarContext import androidx.car.app.hardware.CarHardwareManager import androidx.car.app.hardware.common.CarValue @@ -26,6 +28,7 @@ import androidx.car.app.hardware.info.EnergyLevel class CarHardware(private val carContext: CarContext) { + @RequiresApi(Build.VERSION_CODES.P) fun demoCarInfo() { // [START android_cars_apps_library_car_hardware_info] val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo @@ -45,6 +48,7 @@ class CarHardware(private val carContext: CarContext) { // [END android_cars_apps_library_car_hardware_info] } + @RequiresApi(Build.VERSION_CODES.P) fun demoCarSensors() { // [START android_cars_apps_library_car_hardware_sensors] val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors diff --git a/cars/src/main/java/com/example/cars/communication/TemplatedMessaging.kt b/cars/src/main/java/com/example/cars/communication/TemplatedMessaging.kt index 71c4a7feb..f901d703c 100644 --- a/cars/src/main/java/com/example/cars/communication/TemplatedMessaging.kt +++ b/cars/src/main/java/com/example/cars/communication/TemplatedMessaging.kt @@ -22,15 +22,12 @@ import androidx.car.app.messaging.model.CarMessage import androidx.car.app.messaging.model.ConversationCallback import androidx.car.app.messaging.model.ConversationItem import androidx.car.app.model.Action -import androidx.car.app.model.CarIcon import androidx.car.app.model.CarText import androidx.car.app.model.Header import androidx.car.app.model.ItemList import androidx.car.app.model.ListTemplate import androidx.car.app.model.Template import androidx.core.app.Person -import androidx.core.graphics.drawable.IconCompat -import com.example.cars.R class MyMessage( val sender: Person, @@ -45,10 +42,22 @@ class MyConversation(val id: String, val title: String) { // [START android_cars_communication_templated_messaging_screen] class MyMessagingScreen(carContext: CarContext) : Screen(carContext) { + // [START_EXCLUDE silent] + private val myConversationCallback = object : ConversationCallback { + override fun onMarkAsRead() { + // Implement mark as read logic + } + override fun onTextReply(p0: String) { + // Implement text reply logic + } + } + + private fun getConversations(): List = TODO("Not implemented yet") + // [END_EXCLUDE silent] override fun onGetTemplate(): Template { val itemListBuilder = ItemList.Builder() - val conversations: List = emptyList() // Retrieve conversations + val conversations: List = getConversations() // Retrieve conversations for (conversation: MyConversation in conversations) { val carMessages: List = conversation.getMessages() @@ -64,35 +73,16 @@ class MyMessagingScreen(carContext: CarContext) : Screen(carContext) { } itemListBuilder.addItem( - ConversationItem.Builder() - .setConversationCallback(object : ConversationCallback { - override fun onMarkAsRead() { - // Implement mark as read logic - } - - override fun onTextReply(replyText: String) { - // Implement text reply logic - } - }) - .setId(conversation.id) - .setTitle(CarText.create(conversation.title)) - .setIcon( - CarIcon.Builder( - IconCompat.createWithResource(carContext, R.drawable.ic_garage) - ).build() - ) - .setMessages(carMessages) - /* When the sender of a CarMessage is equal to this Person, - message readout is adjusted to "you said" instead of " - said" */ - .setSelf( - Person.Builder() - .setName("Me") - .setKey("self_id") - .build() - ) - // Set to true if the message contains more than 2 participants - .setGroupConversation(false) + ConversationItem.Builder( + conversation.id, + CarText.create(conversation.title), + Person.Builder() + .setName("Me") + .setKey("self_id") + .build(), + carMessages, + myConversationCallback + ) .build() ) } From bd487517a23b8f133b90552d245688e0f3757862 Mon Sep 17 00:00:00 2001 From: Jan Kleinert Date: Fri, 5 Jun 2026 16:06:47 -0400 Subject: [PATCH 3/8] Update cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../main/java/com/example/cars/apps/library/CarMicrophone.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt b/cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt index f8aa65425..eda38dcc5 100644 --- a/cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt +++ b/cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt @@ -69,8 +69,8 @@ class CarMicrophone(private val carContext: CarContext) { } .build() - if (carContext.getSystemService(AudioManager::class.java) - .requestAudioFocus(audioFocusRequest) + val audioManager = carContext.getSystemService(AudioManager::class.java) + if (audioManager == null || audioManager.requestAudioFocus(audioFocusRequest) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED ) { // Don't record if the focus isn't granted From 9dbfe08fdff52f23a5df4a6e4a6c11eb88bf6180 Mon Sep 17 00:00:00 2001 From: Jan Kleinert Date: Fri, 5 Jun 2026 16:12:58 -0400 Subject: [PATCH 4/8] Update cars/src/main/java/com/example/cars/parked/ParkedAuto.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- cars/src/main/java/com/example/cars/parked/ParkedAuto.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cars/src/main/java/com/example/cars/parked/ParkedAuto.kt b/cars/src/main/java/com/example/cars/parked/ParkedAuto.kt index a9e1a0112..394749214 100644 --- a/cars/src/main/java/com/example/cars/parked/ParkedAuto.kt +++ b/cars/src/main/java/com/example/cars/parked/ParkedAuto.kt @@ -27,7 +27,7 @@ class ParkedAuto { fun checkAndroidAuto(context: Context, connectionType: Int) { val isRunningOnAndroidAuto: Boolean // [START android_cars_parked_auto_detect] - val displayId = context.display.displayId + val displayId = context.display?.displayId ?: DEFAULT_DISPLAY isRunningOnAndroidAuto = (connectionType == CONNECTION_TYPE_PROJECTION) && (displayId != DEFAULT_DISPLAY) // [END android_cars_parked_auto_detect] } From 89bc0b68d63b12cf9a624ed16bf0c1a6405604a1 Mon Sep 17 00:00:00 2001 From: jankleinert <651728+jankleinert@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:15:26 +0000 Subject: [PATCH 5/8] Apply Spotless --- .../main/java/com/example/cars/apps/library/CarMicrophone.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt b/cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt index eda38dcc5..17ca18732 100644 --- a/cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt +++ b/cars/src/main/java/com/example/cars/apps/library/CarMicrophone.kt @@ -70,7 +70,8 @@ class CarMicrophone(private val carContext: CarContext) { .build() val audioManager = carContext.getSystemService(AudioManager::class.java) - if (audioManager == null || audioManager.requestAudioFocus(audioFocusRequest) + if (audioManager == null || + audioManager.requestAudioFocus(audioFocusRequest) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED ) { // Don't record if the focus isn't granted From a0cd2907e1f710b6b9617a2b700279af89a556c8 Mon Sep 17 00:00:00 2001 From: Jan Kleinert Date: Mon, 8 Jun 2026 12:18:46 -0400 Subject: [PATCH 6/8] Update cars/src/main/java/com/example/cars/apps/library/InlineCarIcons.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../main/java/com/example/cars/apps/library/InlineCarIcons.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cars/src/main/java/com/example/cars/apps/library/InlineCarIcons.kt b/cars/src/main/java/com/example/cars/apps/library/InlineCarIcons.kt index d8768583e..dcb68b3e2 100644 --- a/cars/src/main/java/com/example/cars/apps/library/InlineCarIcons.kt +++ b/cars/src/main/java/com/example/cars/apps/library/InlineCarIcons.kt @@ -41,8 +41,8 @@ class InlineCarIcons(private val carContext: CarContext) { ), // The start index of the span (index of the character '4') 8, - // The end index of the span (index of the last 's' in "stars") - 16, + // The end index of the span (exclusive, length of the string) + 17, Spanned.SPAN_INCLUSIVE_INCLUSIVE ) From ded6c491318bd583527baa3b1ac7bebbd9680bfe Mon Sep 17 00:00:00 2001 From: Jan Kleinert Date: Mon, 8 Jun 2026 12:45:23 -0400 Subject: [PATCH 7/8] Update cars/src/main/java/com/example/cars/parked/ParkedAaos.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- cars/src/main/java/com/example/cars/parked/ParkedAaos.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cars/src/main/java/com/example/cars/parked/ParkedAaos.kt b/cars/src/main/java/com/example/cars/parked/ParkedAaos.kt index ed6b6ad84..180d2a663 100644 --- a/cars/src/main/java/com/example/cars/parked/ParkedAaos.kt +++ b/cars/src/main/java/com/example/cars/parked/ParkedAaos.kt @@ -26,9 +26,9 @@ class ParkedAaos(private val context: Context) { fun checkUxRestrictions() { // [START android_cars_parked_automotive_os_ux_restrictions] - val car = Car.createCar(context) + val car = Car.createCar(context) ?: return val carUxRestrictionsManager = - car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager + car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as? CarUxRestrictionsManager ?: return // You can either read the state directly ... val currentUxRestrictions = carUxRestrictionsManager.currentCarUxRestrictions From ccb75ee27ac96f636ea2faee8ddc0827566773e2 Mon Sep 17 00:00:00 2001 From: Jan Kleinert Date: Mon, 8 Jun 2026 16:45:49 +0000 Subject: [PATCH 8/8] Migrate another set of Cars snippets --- .../main/java/com/example/cars/apps/library/ConnectionApi.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cars/src/main/java/com/example/cars/apps/library/ConnectionApi.kt b/cars/src/main/java/com/example/cars/apps/library/ConnectionApi.kt index 8d6c29ffc..3d7737ac4 100644 --- a/cars/src/main/java/com/example/cars/apps/library/ConnectionApi.kt +++ b/cars/src/main/java/com/example/cars/apps/library/ConnectionApi.kt @@ -25,7 +25,7 @@ import androidx.car.app.connection.CarConnection import androidx.car.app.model.MessageTemplate import androidx.car.app.model.Template -class MyConnectionSession(carContext: CarContext) : Session() { +class MyConnectionSession() : Session() { override fun onCreateScreen(intent: Intent): Screen { // [START android_cars_apps_library_connection_api_observe] CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)