1. Before you begin
What this isn't
- A guide on how to create media (audio - e.g. music, radio, podcasts) apps for Android Auto and Android Automotive OS. See Build media apps for cars for details on how to build such apps.
What you'll need
- The most recent Android Studio.
- Experience with basic Kotlin.
- Experience creating Android Virtual Devices and running them in the Android Emulator.
- Basic knowledge of Jetpack Compose.
- An understanding of how to build parked apps for Android Automotive OS, such as covered in the Build and test a parked app for Android Automotive OS codelab.
What you'll build
In this codelab, you'll learn how to add support for audio while driving in Road Reels, an existing app that supports both mobile and Android Automotive OS devices.
The starting version of the pausing playback while user experience restrictions are active. | The completed version of the app continues playback while user experience restrictions are active. |
What you'll learn
- How to enable background audio playback in a video app on Android Automotive OS
2. Get set up
Get the code
- The code for this codelab can be found in the
build-a-parked-app
directory within thecar-codelabs
GitHub repository. To clone it, run the following command:
git clone https://github.com/android/car-codelabs.git
- Alternatively, you can download the repository as a ZIP file:
Open the project
- After starting Android Studio, import the project, selecting just the
build-a-parked-app/end
directory. Thebuild-a-parked-app/audio-while-driving
directory contains the solution code, which you can reference at any point if you get stuck or just want to see the full project.
Familiarize yourself with the code
- After opening the project in Android studio, take some time to look through the starting code.
3. Learn about parked apps for Android Automotive OS
Parked apps make up a subset of the app categories supported by Android Automotive OS. At the time of writing, they consist of video streaming apps, web browsers, and games. These apps are a great fit in cars given the hardware present in vehicles with Google built-in and the increasing prevalence of electric vehicles, in which charging time represents a great opportunity for drivers and passengers to engage with these types of apps.
In many ways, cars are similar to other large screen devices like tablets and foldables. They have touchscreens with similar sizes, resolutions, and aspect ratios and which may be in either portrait or landscape orientation (though, unlike tablets, their orientation is fixed). They are also connected devices which may come in and out of network connection. With all that in mind, it's not surprising that apps which are already optimized for large screens often require a minimal amount of work to bring a great user experience to cars.
Similar to large screens, there are also app quality tiers for apps in cars:
- Tier 3 - Car ready: Your app is large screen compatible and can be used while the car is parked. While it may not have any car-optimized features, users can experience the app just as they would on any other large screen Android device. Mobile apps that meet these requirements are eligible to be distributed to cars as-is through the Car ready mobile apps program.
- Tier 2 - Car optimized: Your app provides a great experience on the car's center stack display. To accomplish this, your app will have some car-specific engineering to include capabilities that can be used across driving or parked modes, depending on your app's category.
- Tier 1- Car differentiated: Your app is built to work across the variety of hardware in cars and can adapt its experience across driving and parked modes. It provides the best user experience designed for the different screens in cars such as the center console, instrument cluster, and additional screens - like panoramic displays seen in many premium cars.
In this codelab, you'll be implementing audio-while driving, which is a Tier 1 feature, making the app a Car differentiated app.
4. Run the app in the Android Automotive OS emulator
Install the Automotive with Play Store System images
- First, open the SDK Manager in Android Studio and select the SDK Platforms tab if it is not already selected. In the bottom-right corner of the SDK Manager window, make sure that the Show package details box is checked.
- Install one of the API 34 Android Automotive emulator images listed in Add generic system images. Images can only run on machines with the same architecture (x86/ARM) as themselves.
Create an Android Automotive OS Android Virtual Device
- After opening the Device Manager, select Automotive under the Category column on the left side of the window. Then, select the Automotive (1408p landscape) bundled hardware profile from the list and click Next.
- On the next page, select the system image from the previous step. Click Next and select any advanced options you want before finally creating the AVD by clicking Finish.
Run the app
Run the app on the emulator you just created using the app
run configuration. Test the behavior of the app by navigating to the player screen and simulating driving.
5. Detect support for audio while driving
Because audio while driving isn't supported on all vehicles, you'll need to detect if the current device supports the feature and adapt your app's behavior correspondingly. To do this, you can use the CarFeatures
class from the androidx.car.app:app
library.
- Add a dependency on the
androidx.car.app:app
artifact.
libs.version.toml
[versions]
...
carApp = "1.7.0-rc01"
[libraries]
...
androidx-car-app = { group = "androidx.car.app", name = "app", version.ref = "carApp" }
build.gradle.kts (Module :app)
implementation(libs.androidx.car.app)
- In
RoadReelsPlayer
, you can then determine if the feature is supported and update the logic used to calculate the value ofshouldPreventPlay
.
RoadReelsPlayer.kt
if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
...
val isBackgroundAudioWhileDrivingSupported = CarFeatures.isFeatureEnabled(
context,
CarFeatures.FEATURE_BACKGROUND_AUDIO_WHILE_DRIVING
)
shouldPreventPlay = !isBackgroundAudioWhileDrivingSupported &&
carUxRestrictionsManager.currentCarUxRestrictions.isRequiresDistractionOptimization
invalidateState()
carUxRestrictionsManager.registerListener { carUxRestrictions: CarUxRestrictions ->
shouldPreventPlay = !isBackgroundAudioWhileDrivingSupported &&
carUxRestrictions.isRequiresDistractionOptimization
...
}
}
Run the app again with these changes and simulate driving. Notice that audio playback now continues when the app's UI is obscured by the system. However, you're not done yet - to meet all of the requirements, you'll need to make a few more changes.
6. Support background playback
Currently, the app's media playback is handled by an activity. As such, media playback may continue for a short period of time after the user experience restrictions become active and the activity is obscured, but your app will eventually be cached by the system, causing playback to end.
To support long-running playback, the app must be updated to use a service to handle the playback. This can be achieved using Media3 MediaSessionService
.
Create a MediaSessionService
- Right click on the
com.example.android.cars.roadreels
package in the Project window and select New > Kotlin Class/File. EnterPlaybackService
as the file name as the name and click the Class type. - Add the following implementation of the
PlaybackService.
See Background playback with a MediaSessionService for more information onMediaSessionService
.
PlaybackService.kt
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
@UnstableApi
class PlaybackService : MediaSessionService() {
private var mediaSession: MediaSession? = null
override fun onCreate() {
super.onCreate()
val player = RoadReelsPlayer(this)
mediaSession = MediaSession.Builder(this, player).build()
}
override fun onDestroy() {
mediaSession?.run {
player.release()
release()
mediaSession = null
}
super.onDestroy()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
if (controllerInfo.isTrusted || controllerInfo.packageName == packageName) {
return mediaSession
}
return null
}
}
- Add the following
<uses-permission>
elements in the manifest file. Media playback is handled using a foreground service
AndroidManifest.xml
<manifest ...>
...
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
...
</manifest>
- Declare the
PlaybackService
in the manifest as well:
AndroidManifest.xml
<application ...>
...
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
</application>
Update the PlayerViewModel to use the PlaybackService
- Because the
PlaybackService
creates and exposes aMediaSession
itself, there's no longer a need for thePlayerViewModel
to create one. Find and delete the following line and all of the references to the variable:
PlayerViewModel.kt
private var mediaSession: MediaSession? = null
- Next, replace the section of the
init
block that instantiates aRoadReelsPlayer
with the following code that connects the app to thePlaybackService
using aMediaController
.
PlayerViewModel.kt
import android.content.ComponentName
import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken
import com.example.android.cars.roadreels.PlaybackService
import com.google.common.util.concurrent.MoreExecutors
...
init {
viewModelScope.launch {
...
}
val sessionToken = SessionToken(
application,
ComponentName(application, PlaybackService::class.java)
)
val mediaControllerFuture =
MediaController.Builder(getApplication(), sessionToken).buildAsync()
mediaControllerFuture.addListener(
{ _player.update { mediaControllerFuture.get() } },
MoreExecutors.directExecutor()
)
}
Test the app again, and you should see a different UI appear when the app is blocked by the system. Now, the user is able to control the playback while they're in motion. And when they're back at rest, they can click the exit button to return to the full app experience.
Remove lifecycle playback changes
Because background playback is now supported, you can remove the two LifecycleEventEffect
blocks in PlayerScreen.kt
so that playback can continue when the user leaves the app, which includes when the playback controls shown in the previous screen are shown.
Pause playback when leaving the player screen
Because the actual player (which is now contained within the PlaybackService
) isn't released when leaving the player screen, you'll need to add a call to pause playback when navigating away from it to maintain the prior behavior. To do this, you can update the implementation of the PlayerViewModel
's onCleared
method:
PlayerViewModel.kt
override fun onCleared() {
super.onCleared()
_player.value?.apply {
pause()
release()
}
}
7. Update the manifest
Lastly, to indicate that your app supports audio while driving, you need to add the following <uses-feature>
element in your app's manifest.
AndroidManifest.xml
<application>
<uses-feature android:name="com.android.car.background_audio_while_driving" android:required="false">
</application>
8. Congratulations
You successfully added support for audio-while-driving to a video app. Now it's time to take what you learned and apply it to your own app!
Further reading
- Build parked apps for cars
- Build video apps for Android Automotive OS
- Add support for Android Automotive OS to your parked app
- The Android app quality for cars page describes the criteria that your app must meet to create a great user experience and pass Play Store review. Make sure to filter for your app's category.