The
Android for Cars App Library
lets you bring your navigation, point of interest (POI), and internet of things (IOT)
apps to the car.
It does so by providing a set of templates designed to meet driver distraction
standards and taking care of details like the variety of car screen factors
and input modalities.
This guide provides an overview of the library's key features and concepts and
walks you through the process of setting up a simple app. For a complete
step-by-step introduction, check out the
Learn Car App Library fundamentals codelab
.
Before you begin
- Review the
Design for Driving
pages covering the Car App Library
- Review the
key terms and concepts
in the following
section.
- Familiarize yourself with the
Android Auto System
UI
and
Android Automotive OS
design
.
- Review the
Release Notes
.
- Review the
Samples
.
Key terms and concepts
- Models and Templates
- The user interface is represented by a graph of model objects that can be
arranged together in different ways, as allowed by the template they belong
to. Templates are a subset of the models that can act as a root in those
graphs. Models include the information to be displayed to the user in the
form of text and images as well as attributes to configure aspects of the
visual appearance of such information—for example, text colors or image
sizes. The host converts the models to views that are designed to meet
driver distraction standards and takes care of details like the variety
of car screen factors and input modalities.
- Host
- The host is the backend component that implements the functionality offered
by the library's APIs so your app can run in the car. The
responsibilities of the host range from discovering your app and managing
its lifecycle to converting your models into views and notifying your app
of user interactions. On mobile devices, this host is implemented by Android
Auto. On Android Automotive OS, this host is installed as a system app.
- Template restrictions
- Different templates enforce restrictions in the content of their models. For
example, list templates have limits on the number of items that can be
presented to the user. Templates also have restrictions in the way they can
be connected to form the flow of a task. For example, the app can only push
up to five templates to the screen stack. See
Template restrictions
for more details.
Screen
Screen
is a class provided by the
library that apps implement to manage the user interface presented to the
user. A
Screen
has a
lifecycle
and provides the mechanism for the app to
send the template to display when the screen is visible.
Screen
instances can also be pushed
and popped to and from a
Screen
stack
, which
ensures they adhere to the
template flow restrictions
.
CarAppService
CarAppService
is an
abstract
Service
class that your app
must implement and export to be discovered and managed by the host.
Your app's
CarAppService
is
responsible for validating that a host connection can be trusted using
createHostValidator
and subsequently providing
Session
instances for each connection using
onCreateSession
.
Session
Session
is an abstract class that
your app must implement and return using
CarAppService.onCreateSession
.
It serves as the entry point to display information on the car screen. It
has a
lifecycle
that informs the
current state of your app on the car screen, such as when your app is
visible or hidden.
When a
Session
is started, such as
when the app is first launched, the host requests for the initial
Screen
to display using the
onCreateScreen
method.
Install the Car App Library
See the Jetpack library
release page
for
instructions on how to add the library to your app.
Before you can create your car app, configure your app’s
manifest files
as follows.
Declare your CarAppService
The host connects to your app through your
CarAppService
implementation. You
declare this service in your manifest to let the host discover and connect
to your app.
You also need to declare your app’s category in the
<category>
element of your app’s
intent filter. See the list of
supported app categories
for the values allowed for
this element.
The following code snippet shows how to declare a car app service for a point of
interest app in your manifest:
<application>
...
<service
...
android:name=".MyCarAppService"
android:exported="true">
<intent-filter>
<action android:name="androidx.car.app.CarAppService"/>
<category android:name="androidx.car.app.category.POI"/>
</intent-filter>
</service>
...
<application>
Supported app categories
Declare your app's category by adding one or more of the following category
values in the intent filter when you declare your
CarAppService
as described
in
the preceding section
:
androidx.car.app.category.NAVIGATION
: an app that provides turn-by-turn
navigation directions.
Check out
Build navigation apps for cars
for additional documentation on this category.
androidx.car.app.category.POI
: an app that provides functionality relevant
to finding points of interest such as parking spots, charging stations, and
gas stations. Check out
Build point of interest apps for cars
for
additional documentation on this category.
androidx.car.app.category.IOT
: An app that enable users to take relevant
actions on connected devices from within the car. Check out
Build internet of things apps for cars
for
additional documentation on this category.
See
Android app quality for cars
for
detailed descriptions of each category and criteria for apps to belong to them.
Specify the app name and icon
You need to specify an app name and icon that the host can use to represent your
app in the system UI.
You can specify the app name and icon that is used to represent your app using
the
label
and
icon
attributes of your
CarAppService
:
...
<service
android:name=".MyCarAppService"
android:exported="true"
android:label="@string/my_app_name"
android:icon="@drawable/my_app_icon">
...
</service>
...
If the label or icon are not declared in the
<service>
element, the host
falls back to the values specified for the
<application>
element.
Set a custom theme
To set a custom theme for your car app, add a
<meta-data>
element in your
manifest file, as follows:
<meta-data
android:name="androidx.car.app.theme"
android:resource="@style/
MyCarAppTheme
/>
Then, declare your
style resource
to
set the following attributes for your custom car app theme:
<resources>
<style name="
MyCarAppTheme
">
<item name="carColorPrimary">@layout/
my_primary_car_color
</item>
<item name="carColorPrimaryDark">@layout/
my_primary_dark_car_color
</item>
<item name="carColorSecondary">@layout/
my_secondary_car_color
</item>
<item name="carColorSecondaryDark">@layout/
my_secondary_dark_car_color
</item>
<item name="carPermissionActivityLayout">@layout/
my_custom_background
</item>
</style>
</resources>
Car App API level
The Car App Library defines its own API levels so that you can know which
library features are supported by the template host on a vehicle.
To retrieve the highest Car App API Level supported by a host, use the
getCarAppApiLevel()
method.
Declare the minimum Car App API Level supported by your app in your
AndroidManifest.xml
file:
<manifest ...>
<application ...>
<meta-data
android:name="androidx.car.app.minCarApiLevel"
android:value="1"/>
</application>
</manifest>
See the documentation for the
RequiresCarApi
annotation for details on how to maintain backward compatibility and declare
the minimum API level required to use a feature. For a definition of which API
level is required to use a certain feature of the Car App Library, check the
reference documentation for
CarAppApiLevels
.
Create your CarAppService and Session
Your app needs to extend the
CarAppService
class and implement
its
onCreateSession
method, which returns a
Session
instance corresponding to the current connection to the host:
Kotlin
class HelloWorldService : CarAppService() {
...
override fun onCreateSession(): Session {
return HelloWorldSession()
}
...
}
Java
public final class HelloWorldService extends CarAppService {
...
@Override
@NonNull
public Session onCreateSession() {
return new HelloWorldSession();
}
...
}
The
Session
instance is responsible for
returning the
Screen
instance to use the
first time the app is started:
Kotlin
class HelloWorldSession : Session() {
...
override fun onCreateScreen(intent: Intent): Screen {
return HelloWorldScreen(carContext)
}
...
}
Java
public final class HelloWorldSession extends Session {
...
@Override
@NonNull
public Screen onCreateScreen(@NonNull Intent intent) {
return new HelloWorldScreen(getCarContext());
}
...
}
To handle scenarios where your car app needs to start from a screen that is not
the home or landing screen of your app, such as handling deep links, you can
pre-seed a back stack of screens using
ScreenManager.push
before returning from
onCreateScreen
.
Pre-seeding allows users to navigate back to previous screens from the first
screen that your app is showing.
Create your start screen
You create the screens displayed by your app by defining classes that extend the
Screen
class and implementing its
onGetTemplate
method, which returns the
Template
instance representing
the state of the UI to display in the car screen.
The following snippet shows how to declare a
Screen
that uses a
PaneTemplate
template to
display a simple “Hello world!” string:
Kotlin
class HelloWorldScreen(carContext: CarContext) : Screen(carContext) {
override fun onGetTemplate(): Template {
val row = Row.Builder().setTitle("Hello world!").build()
val pane = Pane.Builder().addRow(row).build()
return PaneTemplate.Builder(pane)
.setHeaderAction(Action.APP_ICON)
.build()
}
}
Java
public class HelloWorldScreen extends Screen {
@NonNull
@Override
public Template onGetTemplate() {
Row row = new Row.Builder().setTitle("Hello world!").build();
Pane pane = new Pane.Builder().addRow(row).build();
return new PaneTemplate.Builder(pane)
.setHeaderAction(Action.APP_ICON)
.build();
}
}
The CarContext class
The
CarContext
class is a
ContextWrapper
subclass
accessible to your
Session
and
Screen
instances. It provides access
to car services, such as the
ScreenManager
for managing the
screen stack
; the
AppManager
for general app-related
functionality, such as accessing the
Surface
object for
drawing your
navigation app’s map
; and the
NavigationManager
used by turn-by-turn navigation apps to communicate
navigation
metadata
and other
navigation-related
events
with
the host.
See
Access the navigation
templates
for a
comprehensive list of library functionality available to navigation apps.
CarContext
also offers other
functionality, such as letting you load drawable resources using the configuration
from the car screen,
starting an app in the car
using intents,
and signaling whether your navigation app should display its map
in
dark mode
.
Implement screen navigation
Apps often present a number of different screens, each possibly using
different templates the user can navigate through as they interact with
the interface displayed in the screen.
The
ScreenManager
class provides
a screen stack you can use to push screens that can be popped automatically
when the user selects a back button in the car screen or uses the hardware back
button available in some cars.
The following snippet shows how to add a back action to a message template as
well as an action that pushes a new screen when selected by the user:
Kotlin
val template = MessageTemplate.Builder("Hello world!")
.setHeaderAction(Action.BACK)
.addAction(
Action.Builder()
.setTitle("Next screen")
.setOnClickListener { screenManager.push(NextScreen(carContext)) }
.build())
.build()
Java
MessageTemplate template = new MessageTemplate.Builder("Hello world!")
.setHeaderAction(Action.BACK)
.addAction(
new Action.Builder()
.setTitle("Next screen")
.setOnClickListener(
() -> getScreenManager().push(new NextScreen(getCarContext())))
.build())
.build();
The
Action.BACK
object is a
standard
Action
that automatically
invokes
ScreenManager.pop
.
This behavior can be overridden by using the
OnBackPressedDispatcher
instance available from the
CarContext
.
To help ensure the app is safe to use while driving, the screen stack can have
a maximum
depth of five screens. See the
Template restrictions
section for more details.
Refresh the contents of a template
Your app can request the content of a
Screen
to be invalidated by calling the
Screen.invalidate
method.
The host subsequently calls back into your app’s
Screen.onGetTemplate
method to retrieve the template with the new contents.
When refreshing a
Screen
, it is
important to understand the specific content in the template that can be updated
so the host does not count the new template against the template quota.
See the
Template restrictions
section for more details.
We recommended that you structure your screens so there is a one-to-one
mapping between a
Screen
and the type of
template it returns through its
onGetTemplate
implementation.
Interact with the user
Your app can interact with the user using patterns similar to a mobile app.
Your app can respond to user input by passing the appropriate listeners to the
models that support them. The following snippet shows how to create an
Action
model that sets an
OnClickListener
that
calls back to a method defined by your app’s code:
Kotlin
val action = Action.Builder()
.setTitle("Navigate")
.setOnClickListener(::onClickNavigate)
.build()
Java
Action action = new Action.Builder()
.setTitle("Navigate")
.setOnClickListener(this::onClickNavigate)
.build();
The
onClickNavigate
method can then start the
default navigation car app
by using the
CarContext.startCarApp
method:
Kotlin
private fun onClickNavigate() {
val intent = Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address))
carContext.startCarApp(intent)
}
Java
private void onClickNavigate() {
Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address));
getCarContext().startCarApp(intent);
}
For more details on how to start apps, including the format of the
ACTION_NAVIGATE
intent, see the
Start a car app with an intent
section.
Some actions, such as those that require directing the user to continue the
interaction on their mobile devices, are only allowed when the car is parked.
You can use the
ParkedOnlyOnClickListener
to implement those actions. If the car is not parked, the host displays an
indication to the user that the action is not allowed in this case. If the car
is parked, the code executes normally. The following snippet shows how to
use the
ParkedOnlyOnClickListener
to open a settings screen on the mobile device:
Kotlin
val row = Row.Builder()
.setTitle("Open Settings")
.setOnClickListener(ParkedOnlyOnClickListener.create(::openSettingsOnPhone))
.build()
Java
Row row = new Row.Builder()
.setTitle("Open Settings")
.setOnClickListener(ParkedOnlyOnClickListener.create(this::openSettingsOnPhone))
.build();
Display notifications
Notifications sent to the mobile device only show up on the car screen if
they are extended with a
CarAppExtender
.
Some notification attributes, such as content title, text, icon, and actions,
can be set in the
CarAppExtender
, overriding the notification's attributes
when they appear on the car screen.
The following snippet shows how to send a notification to the car screen that
displays a different title than the one shown on the mobile device:
Kotlin
val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setContentTitle(titleOnThePhone)
.extend(
CarAppExtender.Builder()
.setContentTitle(titleOnTheCar)
...
.build())
.build()
Java
Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setContentTitle(titleOnThePhone)
.extend(
new CarAppExtender.Builder()
.setContentTitle(titleOnTheCar)
...
.build())
.build();
Notifications can affect the following parts of the user interface:
- A heads-up notification (HUN) may be displayed to the user.
- An entry in the notification center may be added, optionally with a badge
visible in the rail.
- For navigation apps, the notification may be displayed in the rail widget as
described in
Turn-by-turn notifications
.
You can choose how to configure your app's notifications to affect each of
those user interface elements by using the notification's priority, as described
in the
CarAppExtender
documentation.
If
NotificationCompat.Builder.setOnlyAlertOnce
is called with a value of
true
, a high-priority notification displays as a
HUN only once.
For more information on how to design your car app's notifications, see the
Google Design for Driving guide about
Notifications
.
Show toasts
Your app can display a toast using
CarToast
as shown in this snippet:
Kotlin
CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()
Java
CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();
Request permissions
If your app needs access to restricted data or actions—for example,
location—the
standard rules of Android
permissions
apply. To request a permission, you can use the
CarContext.requestPermissions()
method.
The benefit of using
CarContext.requestPermissions()
, as opposed to using
standard Android APIs
, is
that you don't need to launch your own
Activity
to
create the permissions dialog. Moreover, you can use the same code on both
Android Auto and Android Automotive OS, rather than having to create
platform-dependent flows.
Style the permissions dialog on Android Auto
On Android Auto, the permissions dialog for the user will appear on the phone.
By default, there will be no background behind the dialog. To set a custom
background, declare a
car app theme
in your
AndroidManifest.xml
file and set the
carPermissionActivityLayout
attribute
for your car app theme.
<meta-data
android:name="androidx.car.app.theme"
android:resource="@style/
MyCarAppTheme
/>
Then, set the
carPermissionActivityLayout
attribute for your car app theme:
<resources>
<style name="
MyCarAppTheme
">
<item name="carPermissionActivityLayout">@layout/
my_custom_background
</item>
</style>
</resources>
Start a car app with an intent
You can call the
CarContext.startCarApp
method to perform one of the following actions:
- Open the dialer to make a phone call.
- Start turn-by-turn navigation to a location with the
default navigation car app
.
- Start your own app with an intent.
The following example shows how to create a notification with an action that
opens your app with a screen that shows the details of a parking reservation.
You extend the notification instance with a content intent that contains a
PendingIntent
wrapping an explicit
intent to your app's action:
Kotlin
val notification = notificationBuilder
...
.extend(
CarAppExtender.Builder()
.setContentIntent(
PendingIntent.getBroadcast(
context,
ACTION_VIEW_PARKING_RESERVATION.hashCode(),
Intent(ACTION_VIEW_PARKING_RESERVATION)
.setComponent(ComponentName(context, MyNotificationReceiver::class.java)),
0))
.build())
Java
Notification notification = notificationBuilder
...
.extend(
new CarAppExtender.Builder()
.setContentIntent(
PendingIntent.getBroadcast(
context,
ACTION_VIEW_PARKING_RESERVATION.hashCode(),
new Intent(ACTION_VIEW_PARKING_RESERVATION)
.setComponent(new ComponentName(context, MyNotificationReceiver.class)),
0))
.build());
Your app must also declare a
BroadcastReceiver
that is
invoked to process the intent when the user selects the action in the
notification interface and invokes
CarContext.startCarApp
with an intent including the data URI:
Kotlin
class MyNotificationReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val intentAction = intent.action
if (ACTION_VIEW_PARKING_RESERVATION == intentAction) {
CarContext.startCarApp(
intent,
Intent(Intent.ACTION_VIEW)
.setComponent(ComponentName(context, MyCarAppService::class.java))
.setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)))
}
}
}
Java
public class MyNotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String intentAction = intent.getAction();
if (ACTION_VIEW_PARKING_RESERVATION.equals(intentAction)) {
CarContext.startCarApp(
intent,
new Intent(Intent.ACTION_VIEW)
.setComponent(new ComponentName(context, MyCarAppService.class))
.setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)));
}
}
}
Finally, the
Session.onNewIntent
method in your app handles this intent by pushing the parking reservation screen
on the stack, if it's not already on top:
Kotlin
override fun onNewIntent(intent: Intent) {
val screenManager = carContext.getCarService(ScreenManager::class.java)
val uri = intent.data
if (uri != null
&& MY_URI_SCHEME == uri.scheme
&& MY_URI_HOST == uri.schemeSpecificPart
&& ACTION_VIEW_PARKING_RESERVATION == uri.fragment
) {
val top = screenManager.top
if (top !is ParkingReservationScreen) {
screenManager.push(ParkingReservationScreen(carContext))
}
}
}
Java
@Override
public void onNewIntent(@NonNull Intent intent) {
ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
Uri uri = intent.getData();
if (uri != null
&& MY_URI_SCHEME.equals(uri.getScheme())
&& MY_URI_HOST.equals(uri.getSchemeSpecificPart())
&& ACTION_VIEW_PARKING_RESERVATION.equals(uri.getFragment())
) {
Screen top = screenManager.getTop();
if (!(top instanceof ParkingReservationScreen)) {
screenManager.push(new ParkingReservationScreen(getCarContext()));
}
}
}
See the
Display notifications
section for more
information on how to handle notifications for the car app.
Template restrictions
The host limits the number of templates to display for a given task to a maximum
of five, of which the last template must be one of the following types:
Note that this limit applies to the number of templates and not the number of
Screen
instances in the stack. For
example, if an app sends two templates while in screen A and then pushes screen
B, it can now send three more templates. Alternatively, if each screen is structured
to send a single template, then the app can push five screen instances onto the
ScreenManager
stack.
There are special cases to these restrictions: template refreshes and back and
reset operations.
Template refreshes
Certain content updates are not counted toward the template limit. In general,
if an app pushes a new template that is of the same type and contains
the same main content as the previous template, the new template is not
counted against the quota. For example, updating the toggle state of a row in a
ListTemplate
does not count
against the quota. See the documentation of individual templates to learn more
about what types of content updates can be considered a refresh.
Back operations
To enable sub-flows within a task, the host detects when an app is popping a
Screen
from the
ScreenManager
stack and updates
the remaining quota based on the number of templates that the app is going
backward by.
For example, if the app sends two templates while in screen A, then pushes
screen B and sends two more templates, the app has one quota remaining. If
the app then pops back to screen A, the host resets the quota to three, because
the app has gone backward by two templates.
Note that, when popping back to a screen, an app must send a template that is
of the same type as the one last sent by that screen. Sending any other
template type causes an error. However, as long as the type remains the
same during a back operation, an app can freely modify the contents of the
template without affecting the quota.
Reset operations
Certain templates have special semantics that signify the end of a task. For
example, the
NavigationTemplate
is a view that is expected to stay on the screen and be refreshed with new
turn-by-turn instructions for the user's consumption. When it reaches one of these
templates, the host resets the template quota, treating that template as if
it is the first step of a new task. This allows the app to begin a new task.
See the documentation of individual templates to see which ones trigger a reset
on the host.
If the host receives an intent to start the app from a notification action or
from the launcher, the quota is also reset. This mechanism lets an app
begin a new task flow from notifications, and it holds true even if an app is
already bound and in the foreground.
See the
Display notifications
section for more details
on how to display your app's notifications in the car screen. See the
Start a car app with an intent
section for information on how
to start your app from a notification action.
Connection API
You can determine whether your app is running on Android Auto or Android
Automotive OS by using the
CarConnection
API
to
retrieve connection information at runtime.
For example, in your car app's
Session
, initialize a
CarConnection
and
subscribe to
LiveData
updates:
Kotlin
CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)
Java
new CarConnection(getCarContext()).getType().observe(this, this::onConnectionStateUpdated);
In the observer, you can then react to changes in the connection state:
Kotlin
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()
}
Java
private void onConnectionStateUpdated(int connectionState) {
String message;
switch(connectionState) {
case CarConnection.CONNECTION_TYPE_NOT_CONNECTED:
message = "Not connected to a head unit";
break;
case CarConnection.CONNECTION_TYPE_NATIVE:
message = "Connected to Android Automotive OS";
break;
case CarConnection.CONNECTION_TYPE_PROJECTION:
message = "Connected to Android Auto";
break;
default:
message = "Unknown car connection type";
break;
}
CarToast.makeText(getCarContext(), message, CarToast.LENGTH_SHORT).show();
}
Constraints API
Different cars may allow for a different number of
Item
instances to be displayed to
the user at a time. Use the
ConstraintManager
to check the content limit at runtime and set the appropriate number of items
in your templates.
Start by getting a
ConstraintManager
from the
CarContext
:
Kotlin
val manager = carContext.getCarService(ConstraintManager::class.java)
Java
ConstraintManager manager = getCarContext().getCarService(ConstraintManager.class);
You can then query the retrieved
ConstraintManager
object for the relevant
content limit. For example, to get the number of items that can be displayed in
a grid, call
getContentLimit
with
CONTENT_LIMIT_TYPE_GRID
:
Kotlin
val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)
Java
int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);
Add a sign-in flow
If your app offers a signed-in experience for users, you can use templates like
the
SignInTemplate
and
LongMessageTemplate
with Car App API level 2 and above to handle signing in to your app on the
car's head unit.
To create a
SignInTemplate
, define a
SignInMethod
. The Car
App Library currently supports the following sign-in methods:
For example, to implement a template that collects the user's password, start by
creating an
InputCallback
to process and validate user input:
Kotlin
val callback = object : InputCallback {
override fun onInputSubmitted(text: String) {
// You will receive this callback when the user presses Enter on the keyboard.
}
override fun onInputTextChanged(text: String) {
// You will receive this callback as the user is typing. The update
// frequency is determined by the host.
}
}
Java
InputCallback callback = new InputCallback() {
@Override
public void onInputSubmitted(@NonNull String text) {
// You will receive this callback when the user presses Enter on the keyboard.
}
@Override
public void onInputTextChanged(@NonNull String text) {
// You will receive this callback as the user is typing. The update
// frequency is determined by the host.
}
};
An
InputCallback
is required for the
InputSignInMethod
Builder
.
Kotlin
val passwordInput = InputSignInMethod.Builder(callback)
.setHint("Password")
.setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
...
.build()
Java
InputSignInMethod passwordInput = new InputSignInMethod.Builder(callback)
.setHint("Password")
.setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
...
.build();
Finally, use your new
InputSignInMethod
to create a
SignInTemplate
.
Kotlin
SignInTemplate.Builder(passwordInput)
.setTitle("Sign in with username and password")
.setInstructions("Enter your password")
.setHeaderAction(Action.BACK)
...
.build()
Java
new SignInTemplate.Builder(passwordInput)
.setTitle("Sign in with username and password")
.setInstructions("Enter your password")
.setHeaderAction(Action.BACK)
...
.build();
Use AccountManager
Android Automotive OS apps that have authentication must use
AccountManager
for the following reasons:
- Better UX and ease of account management
: Users can easily manage all
their accounts from the accounts menu in the system settings, including sign-in
and sign-out.
- "Guest" experiences
: Because cars are shared devices, OEMs can enable
guest experiences in the vehicle, where accounts cannot be added.
Add text string variants
Different car screen sizes may show different amounts of text. With Car App API
level 2 and above, you can specify multiple variants of a text string to best
fit the screen. To see where text variants are accepted, look for templates and
components that take a
CarText
.
You can add text string variants to a
CarText
with the
CarText.Builder.addVariant()
method:
Kotlin
val itemTitle = CarText.Builder("This is a very long string")
.addVariant("Shorter string")
...
.build()
Java
CarText itemTitle = new CarText.Builder("This is a very long string")
.addVariant("Shorter string")
...
.build();
You can then use this
CarText
—for example, as the primary text of a
GridItem
.
Kotlin
GridItem.Builder()
.addTitle(itemTitle)
...
.build()
Java
new GridItem.Builder()
.addTitle(itemTitle)
...
build();
Add strings in order from most to least preferred—for example, from longest to
shortest. The host picks the appropriate-length string depending on the
amount of space available on the car screen.
Add inline CarIcons for rows
You can add icons inline with text to enrich your app's visual appeal using
CarIconSpan
.
See the documentation for
CarIconSpan.create
for more information on creating these spans. See
Spantastic
text styling with Spans
for an overview of how text styling with spans work.
Kotlin
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(...).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()
...
.addText(rating)
.build()
Java
SpannableString rating = new SpannableString("Rating: 4.5 stars");
rating.setSpan(
CarIconSpan.create(
// Create a CarIcon with an image of four and a half stars
new CarIcon.Builder(...).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
);
Row row = new Row.Builder()
...
.addText(rating)
.build();
Car Hardware APIs
Starting with Car App API level 3, the Car App Library has APIs that you
can use to access vehicle properties and sensors.
Requirements
To use the APIs with Android Auto, start by adding a dependency on
androidx.car.app:app-projected
to the
build.gradle
file for your Android
Auto module. For Android Automotive OS, add a dependency on
androidx.car.app:app-automotive
to the
build.gradle
file for your Android
Automotive OS module.
Additionally, in your
AndroidManifest.xml
file, you need to
declare the relevant permissions
needed to
request the car data you want to use. Note that these permissions must also be
granted
to you by the user. You can use the
same code
on both Android Auto and Android Automotive OS, rather
than having to create platform-dependent flows. However, the permissions needed
are different.
CarInfo
This table describes the properties surfaced by the
CarInfo
APIs and the
permissions you need to request to use them:
Methods
|
Properties
|
Android Auto Permissions
|
Android Automotive OS Permissions
|
fetchModel
|
Make, model, year
|
|
android.car.permission.CAR_INFO
|
fetchEnergyProfile
|
EV connector types, fuel types
|
com.google.android.gms.permission.CAR_FUEL
|
android.car.permission.CAR_INFO
|
addTollListener
removeTollListener
|
Toll card state, toll card type
|
|
|
addEnergyLevelListener
removeEnergyLevelListener
|
Battery level, fuel level, fuel level low, range remaining
|
com.google.android.gms.permission.CAR_FUEL
|
android.car.permission.CAR_ENERGY
,
android.car.permission.CAR_ENERGY_PORTS
,
android.car.permission.READ_CAR_DISPLAY_UNITS
|
addSpeedListener
removeSpeedListener
|
Raw speed, display speed (shown on car's cluster display)
|
com.google.android.gms.permission.CAR_SPEED
|
android.car.permission.CAR_SPEED
,
android.car.permission.READ_CAR_DISPLAY_UNITS
|
addMileageListener
removeMileageListener
|
Odometer distance
|
com.google.android.gms.permission.CAR_MILEAGE
|
This data is not available on Android Automotive OS to apps installed from the Play Store.
|
For example, to get the remaining range, instantiate a
CarInfo
object, then
create and register an
OnCarDataAvailableListener
:
Kotlin
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
val listener = OnCarDataAvailableListener<EnergyLevel> { 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)
Java
CarInfo carInfo = getCarContext().getCarService(CarHardwareManager.class).getCarInfo();
OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
if(data.getRangeRemainingMeters().getStatus() == CarValue.STATUS_SUCCESS) {
float rangeRemaining = data.getRangeRemainingMeters().getValue();
} else {
// Handle error
}
};
carInfo.addEnergyLevelListener(getCarContext().getMainExecutor(), listener);
…
// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener);
Don't assume that the data from the car is available at all times.
If you get an error, check the
status
of
the value you requested to better understand why the data you requested could
not be retrieved. Refer to the
reference documentation
for
the full
CarInfo
class definition.
CarSensors
The
CarSensors
class
gives you access to the vehicle's accelerometer, gyroscope, compass, and
location data. The availability of these values may depend on the
OEM. The format for the data from the accelerometer, gyroscope, and compass is
the same as you would get from the
SensorManager
API
. For example,
to check the vehicle's heading:
Kotlin
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
val listener = OnCarDataAvailableListener<Compass> { 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)
Java
CarSensors carSensors = getCarContext().getCarService(CarHardwareManager.class).getCarSensors();
OnCarDataAvailableListener<Compass> listener = (data) -> {
if (data.getOrientations().getStatus() == CarValue.STATUS_SUCCESS) {
List<Float> orientations = data.getOrientations().getValue();
} else {
// Data not available, handle error
}
};
carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, getCarContext().getMainExecutor(),
listener);
…
// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener);
To access location data from the car, you also need to declare and request the
android.permission.ACCESS_FINE_LOCATION
permission.
Testing
To simulate sensor data when testing on Android Auto, refer to the
Sensors
and
Sensor
configuration
sections of the
Desktop Head Unit guide. To simulate sensor data when testing on Android
Automotive OS, refer to the
Emulate hardware
state
section of the Android
Automotive OS emulator guide.
The CarAppService, Session and Screen lifecycles
The
Session
and
Screen
classes implement the
LifecycleOwner
interface. As
the user interacts with the app, your
Session
and
Screen
objects' lifecycle
callbacks are invoked, as described in the following diagrams.
The lifecycles of a CarAppService and a Session
For full details, see the documentation for the
Session.getLifecycle
method.
The lifecycle of a Screen
For full details, see the documentation for the
Screen.getLifecycle
method.
Record from the car microphone
Using your app's
CarAppService
and the
CarAudioRecord
API,
you can give your app access to the user's car microphone. Users need to give
your app permission to access the car microphone. Your app can record and
process the user's input within your app.
Permission to record
Before recording any audio, you must first declare the permission to record in your
AndroidManifest.xml
and request that the user grant it.
<manifest ...>
...
<uses-permission android:name="android.permission.RECORD_AUDIO" />
...
</manifest>
You need to request the permission to record at runtime. See the
Request
permissions
section for details on how to request a
permission in your car app.
Record audio
After the user gives permission to record, you can record the audio and process
the recording.
Kotlin
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()
Java
CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
carAudioRecord.startRecording();
byte[] data = new byte[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();
Audio focus
When recording from the car microphone, first acquire
audio
focus
to
ensure that any ongoing media is stopped. If you lose audio focus,
stop recording.
Here is an example of how to acquire audio focus:
Kotlin
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
Java
CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
// Take audio focus so that user's media is not recorded
AudioAttributes audioAttributes =
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
// Use the most appropriate usage type for your use case
.setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.build();
AudioFocusRequest audioFocusRequest =
new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
.setAudioAttributes(audioAttributes)
.setOnAudioFocusChangeListener(state -> {
if (state == AudioManager.AUDIOFOCUS_LOSS) {
// Stop recording if audio focus is lost
carAudioRecord.stopRecording();
}
})
.build();
if (getCarContext().getSystemService(AudioManager.class).requestAudioFocus(audioFocusRequest)
!= AUDIOFOCUS_REQUEST_GRANTED) {
// Don't record if the focus isn't granted
return;
}
carAudioRecord.startRecording();
// Process the audio and abandon the AudioFocusRequest when done
Testing Library
The Android for Cars
Testing
Library
provides auxiliary
classes that you can use to validate your app's behavior in a test environment.
For example, the
SessionController
lets you simulate a connection to the host and verify that the correct
Screen
and
Template
are created and
returned.
Refer to the
Samples
for usage examples.
Report an Android for Cars App Library issue
If you find an issue with the library, report it using the
Google Issue Tracker
.
Be sure to fill out all the requested information in the issue template.
Create a new issue
Before filing a new issue, please check whether it is listed in the library's release
notes or reported in the issues list. You can subscribe and vote for issues by
clicking the star for an issue in the tracker. For more information, see
Subscribing to an Issue
.