使用車輛專用 Android App Library

車輛專用 Android App Library 可讓您將導航、搜尋點和物聯網 (IOT) 應用程式移至車上。為此,我們會提供一組範本,專為滿足駕駛人分心等級的標準設計,並會謹慎處理各種細節,例如各種車輛螢幕因素和輸入模式。

本指南提供程式庫的主要功能和概念總覽,並逐步引導您設定簡易應用程式的程序。如需完整的逐步操作說明,請參閱「瞭解 Car App Library 基礎知識程式碼研究室」。

事前準備

  1. 查看涵蓋 Car App Library 的「Design for Driving」頁面
  2. 請參閱下一節的重要詞彙與概念
  3. 熟悉 Android Auto 系統 UIAndroid Automotive OS 設計
  4. 查看版本資訊
  5. 查看範例

重要詞彙與概念

模型與範本
使用者介面是以模型物件圖表呈現,這些物件可透過各種方式以不同方式排列,如其所屬的範本允許。範本是模型的子集,可以在這些圖形中做為根層級。模型包含要以文字和圖片的形式向使用者顯示的資訊,以及用於設定資訊視覺外觀各個面向 (例如文字顏色或圖片大小) 的屬性。主機會將模型轉換為檢視畫面,這些檢視畫面的設計旨在滿足駕駛人分心等級標準,並負責處理各種細節,例如各種車輛螢幕因素和輸入模式。
主機
主機是後端元件,可實作程式庫 API 提供的功能,讓應用程式可在車輛中執行。主機的責任範圍包括探索應用程式和管理其生命週期、將模型轉換為檢視畫面,以及通知應用程式使用者互動。在行動裝置上,這個主機是由 Android Auto 實作。在 Android Automotive OS 中,此主機會安裝為系統應用程式。
範本限制
不同的範本會強制執行模型內容限制。舉例來說,清單範本對使用者可顯示的項目數量設有限制。範本也設有限制,以連結這些範本以形成工作流程。舉例來說,應用程式最多只能將五個範本推送至畫面堆疊。詳情請參閱「範本限制」一節。
Screen
Screen 是程式庫提供的類別,應用程式會實作這些類別來管理向使用者顯示的使用者介面。Screen 具有生命週期,並提供可讓應用程式在畫面顯示時傳送範本的機制。也可以將 Screen 執行個體推送和從 Screen 堆疊推送和彈出,確保符合範本流程限制
CarAppService
CarAppService 是抽象的 Service 類別,應用程式必須實作及匯出,主機才能探索及管理。應用程式的 CarAppService 會負責使用 createHostValidator 驗證主機連線是否可信任,隨後使用 onCreateSession 為每個連線提供 Session 執行個體。
Session

Session 是抽象類別,應用程式必須使用 CarAppService.onCreateSession 實作及傳回。這可做為在車輛螢幕上顯示資訊的進入點。它具有生命週期,可在車輛螢幕上通知應用程式目前的狀態,例如應用程式是否可見或隱藏。

啟動 Session (例如應用程式首次啟動時) 時,會使用 onCreateScreen 方法要求初始 Screen 顯示的主機。

安裝 Car App Library

如需將程式庫新增至應用程式的操作說明,請參閱 Jetpack 程式庫版本頁面

設定應用程式的資訊清單檔案

建立車用應用程式前,請先按照下列步驟設定應用程式的資訊清單檔案

宣告 CarAppService

主機會透過 CarAppService 實作項目連線至您的應用程式。您必須在資訊清單中宣告這項服務,讓主機探索並連線至您的應用程式。

您還必須在應用程式的意圖篩選器的 <category> 元素中宣告應用程式類別。請參閱支援的應用程式類別清單,瞭解此元素允許的值。

下列程式碼片段說明如何在資訊清單中,為搜尋點應用程式宣告車用應用程式服務:

<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>

支援的應用程式類別

宣告 CarAppService 時,如上一節所述,請在意圖篩選器中新增下列一或多個類別值,以宣告應用程式類別:

  • androidx.car.app.category.NAVIGATION:提供即時路線導航路線的應用程式。如需這個類別的其他說明文件,請參閱「建構車輛專用導航應用程式」一文。
  • androidx.car.app.category.POI:應用程式提供與搜尋點相關的功能,例如停車位、充電站和加油站。如需這個類別的其他說明文件,請參閱「建構車用搜尋點應用程式」。
  • androidx.car.app.category.IOT:可讓使用者在車內對已連結裝置採取相關動作的應用程式。如需這個類別的其他說明文件,請參閱「建構車用物聯網應用程式」。

請參閱「車用 Android 應用程式品質」一文,進一步瞭解各個類別以及應用程式所屬的條件。

指定應用程式名稱和圖示

您必須指定應用程式名稱和圖示,主機可用來在系統 UI 中代表您的應用程式。

您可以使用 CarAppServicelabelicon 屬性,指定用來代表應用程式的應用程式名稱和圖示:

...
<service
   android:name=".MyCarAppService"
   android:exported="true"
   android:label="@string/my_app_name"
   android:icon="@drawable/my_app_icon">
   ...
</service>
...

如果 <service> 元素中未宣告標籤或圖示,主機會改回使用 <application> 元素指定的值。

設定自訂主題

如要為車用應用程式設定自訂主題,請在資訊清單檔案中加入 <meta-data> 元素,如下所示:

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

接著,宣告樣式資源,為自訂車輛應用程式主題設定下列屬性:

<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 級別

Car App Library 會定義自己的 API 級別,方便您瞭解車輛上的範本主機支援哪些程式庫功能。如要擷取主機支援的最高 Car App API 級別,請使用 getCarAppApiLevel() 方法。

AndroidManifest.xml 檔案中宣告應用程式支援的最低車輛應用程式 API 級別:

<manifest ...>
    <application ...>
        <meta-data
            android:name="androidx.car.app.minCarApiLevel"
            android:value="1"/>
    </application>
</manifest>

請參閱 RequiresCarApi 註解的說明文件,進一步瞭解如何維持回溯相容性,並宣告使用某項功能所需的最低 API 級別。如想瞭解使用 Car App Library 特定功能所需的 API 級別,請參閱 CarAppApiLevels 參考說明文件。

建立 CarAppService 和工作階段

您的應用程式需要擴充 CarAppService 類別並實作其 onCreateSession 方法,此方法會傳回與主機目前連線相對應的 Session 執行個體:

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();
    }
    ...
}

Session 執行個體負責傳回 Screen 例項,以便使用應用程式首次啟動時:

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());
    }
    ...
}

如要處理車輛應用程式需要從非應用程式主畫面或到達畫面啟動的情境 (例如處理深層連結),您可以使用 ScreenManager.push 預先種植畫面返回堆疊,然後再從 onCreateScreen 傳回。啟動前言可讓使用者從應用程式顯示的第一個畫面返回之前的畫面。

建立開始畫面

您可以建立應用程式顯示的畫面,方法是定義擴充 Screen 類別的類別,並實作其 onGetTemplate 方法,以傳回 Template 例項,代表要在車輛螢幕上顯示的 UI 狀態。

下列程式碼片段說明如何宣告 Screen,使用 PaneTemplate 範本顯示簡單的「Hello World!」字串:

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();
    }
}

CarContext 類別

CarContext 類別是 ContextWrapper 子類別,可供 SessionScreen 執行個體存取。這個程式庫提供車輛服務的存取權,例如用來管理畫面堆疊ScreenManager、適用於一般應用程式相關功能的 AppManager,例如存取 Surface 物件用於繪製導航應用程式地圖;以及使用NavigationManager即時導航導航應用程式,與其他導覽應用程式通訊和主機相關中繼資料。

如需導航應用程式可用的程式庫功能完整清單,請參閱「存取導覽範本」。

CarContext 也提供其他功能,例如讓您透過車輛螢幕設定載入可繪製資源、使用意圖在車上啟動應用程式,以及指出導航應用程式是否應以深色模式顯示地圖。

實作畫面導覽

應用程式通常會顯示多個不同的畫面,每個畫面都可能使用不同範本,讓使用者在與畫面上顯示的介面互動時,能夠瀏覽。

當使用者在車輛螢幕上選取返回按鈕,或使用部分車款提供的硬體返回按鈕時,ScreenManager 類別提供螢幕堆疊,可用來推送可自動彈出的畫面。

下列程式碼片段說明如何在訊息範本中新增返回動作,以及在使用者選取後推送新畫面的動作:

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();

Action.BACK 物件是會自動叫用 ScreenManager.pop 的標準 Action。您可以使用 CarContext 提供的 OnBackPressedDispatcher 執行個體覆寫這個行為。

為確保行車期間安全使用應用程式,螢幕堆疊的深度上限為五個畫面。詳情請參閱「範本限制」一節。

重新整理範本內容

應用程式可以呼叫 Screen.invalidate 方法,要求無效的 Screen 內容。主機隨後會呼叫應用程式的 Screen.onGetTemplate 方法,使用新內容擷取範本。

重新整理 Screen 時,請務必瞭解範本中可更新的特定內容,這樣主機就不會將新範本計入範本配額。詳情請參閱「範本限制」一節。

建議您建構畫面結構,讓 Screen 和透過其 onGetTemplate 實作傳回的範本類型之間採用一對一對應。

與使用者互動

您的應用程式可以使用類似於行動應用程式的模式與使用者互動。

處理使用者輸入內容

應用程式可將適當的事件監聽器傳遞給支援這些事件監聽器的模型,藉此回應使用者輸入內容。下列程式碼片段說明如何建立 Action 模型,用於設定 OnClickListener,以便回呼應用程式程式碼所定義的方法:

Kotlin

val action = Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(::onClickNavigate)
    .build()

Java

Action action = new Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(this::onClickNavigate)
    .build();

接著,onClickNavigate 方法可以使用 CarContext.startCarApp 方法啟動預設車輛導航應用程式

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);
}

如要進一步瞭解如何啟動應用程式 (包括 ACTION_NAVIGATE 意圖的格式),請參閱「透過意圖啟動車用應用程式」一節。

部分動作 (例如需要引導使用者繼續在行動裝置上進行互動的動作) 只能在車輛停妥後使用。您可以使用 ParkedOnlyOnClickListener 實作這些動作。如果車輛未停妥,主機會向使用者顯示指示,指出在本例中不允許該動作。假如車輛停妥,程式碼會照常執行。下列程式碼片段說明如何使用 ParkedOnlyOnClickListener 在行動裝置上開啟設定畫面:

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();

顯示通知

只有在以 CarAppExtender 擴充至行動裝置的通知時,才會顯示在車輛螢幕上的通知。您可以在 CarAppExtender 中設定部分通知屬性 (例如內容名稱、文字、圖示和動作),覆寫顯示在車輛螢幕上的通知屬性。

下列程式碼片段說明如何傳送通知到顯示標題與行動裝置顯示不同標題的車輛螢幕:

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();

通知可能會影響使用者介面的以下部分:

  • 使用者可能會看見抬頭通知 (HUN)。
  • 您可以新增通知中心的項目,也可以選擇在邊欄內顯示標記。
  • 如為導航應用程式,通知可能會顯示在邊欄小工具中,如即時路線通知所述。

您可以按照 CarAppExtender 說明文件的說明,選擇如何將應用程式的通知設為影響每個使用者介面元素的通知。

如果使用 true 值呼叫 NotificationCompat.Builder.setOnlyAlertOnce,則高優先順序的通知僅會顯示為 HUN 一次。

如要進一步瞭解如何設計車用應用程式的通知,請參閱 Google Design for Driving 的通知相關頁面。

顯示浮動式訊息

應用程式可以使用 CarToast 顯示浮動式訊息,如以下程式碼片段所示:

Kotlin

CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()

Java

CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();

要求權限

如果應用程式需要存取受限資料或動作 (例如位置資訊),則適用 Android 權限的標準規則。您可以使用 CarContext.requestPermissions() 方法要求權限。

使用 CarContext.requestPermissions() (與使用標準 Android API 不同) 的好處是您不需要啟動自己的 Activity 即可建立權限對話方塊。此外,您可以在 Android Auto 和 Android Automotive OS 上使用相同的程式碼,不必建立平台專屬的流程。

在 Android Auto 上設定權限對話方塊的樣式

Android Auto 手機會顯示使用者的權限對話方塊。根據預設,對話方塊後方不會有背景。如要設定自訂背景,請在 AndroidManifest.xml 檔案中宣告車用應用程式主題,並設定車輛應用程式主題的 carPermissionActivityLayout 屬性。

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

然後,設定車用應用程式主題的 carPermissionActivityLayout 屬性:

<resources>
  <style name="MyCarAppTheme">
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

以意圖啟動車用應用程式

您可以呼叫 CarContext.startCarApp 方法來執行下列其中一項操作:

  • 開啟撥號程式撥打電話。
  • 使用預設車輛導航應用程式啟動前往特定地點的即時路線導航。
  • 以意圖啟動自己的應用程式。

以下範例說明如何透過動作開啟應用程式,並顯示顯示停車預訂詳細資料的畫面。請使用內容意圖擴充通知例項,其中包含 PendingIntent 將明確意圖納入應用程式的動作:

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());

當使用者在通知介面中選取動作,並使用包含資料 URI 的意圖叫用 CarContext.startCarApp 時,應用程式也必須宣告用於處理意圖的 BroadcastReceiver

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)));
        }
    }
}

最後,如果未在堆疊上推送停車預訂畫面,應用程式中的 Session.onNewIntent 方法會處理這項意圖:

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()));
        }
    }
}

如要進一步瞭解如何處理車輛應用程式的通知,請參閱「顯示通知」一節。

範本限制

主機限制了特定工作最多顯示的範本數量 (最多 5 個),最後一個範本必須是下列其中一種類型:

請注意,這項限制適用於範本數量,不適用於堆疊中的 Screen 執行個體數量。舉例來說,如果應用程式在畫面 A 中傳送兩個範本,然後推送畫面 B,現在就可以再傳送三個範本。或者,如果每個畫面都是傳送單一範本,則應用程式可以將五個畫面執行個體推送至 ScreenManager 堆疊。

這些限制有一些特殊情況:會重新整理範本,以及執行返回和重設作業。

範本重新整理次數

某些內容更新不會計入範本限制。一般來說,如果應用程式推送類型相同的新範本,且包含與先前範本相同的主要內容,新的範本就不會計入配額。舉例來說,更新 ListTemplate 中資料列的切換狀態不會計入配額。請參閱個別範本的說明文件,進一步瞭解哪些類型的內容更新可視為重新整理。

返回作業

為了在工作中啟用子流程,主機會偵測何時從 ScreenManager 堆疊彈出 Screen,並根據應用程式返回的範本數量更新剩餘的配額。

舉例來說,如果應用程式在畫面 A 中傳送兩個範本,接著推送畫面 B 並傳送兩個範本,則應用程式剩餘的配額。如果應用程式接著彈回畫面 A,主機會將配額重設為 3,因為應用程式已經改回使用兩個範本。

請注意,當彈回畫面時,應用程式必須傳送與該畫面最後一個傳送的相同類型的範本。傳送任何其他範本類型都會導致錯誤。不過,只要類型在返回作業期間保持不變,應用程式就能自由修改範本內容,而不影響配額。

重設作業

某些範本具有特殊的語意,可代表任務的結尾。舉例來說,NavigationTemplate 是一個應留在螢幕上的檢視畫面,會以新的即時路線指示 (供使用者參考) 重新整理。當主機到達其中一個範本時,主機會重設範本配額,並將該範本視為新任務的第一步。這樣做可讓應用程式開始新的工作。請參閱個別範本的說明文件,瞭解哪些範本會在主機上觸發重設。

如果主機收到透過通知動作或啟動器啟動應用程式的意圖,系統也會重設配額。這項機制可讓應用程式從通知開始新的工作流程,即使應用程式已繫結並在前景執行,也會保持 true。

如要進一步瞭解如何在車輛螢幕上顯示應用程式通知,請參閱「顯示通知」一節。如要瞭解如何透過通知動作啟動應用程式,請參閱「透過意圖啟動車用應用程式」一節。

連線 API

您可以使用 CarConnection API 在執行階段擷取連線資訊,判斷應用程式是在 Android Auto 或 Android Automotive OS 上執行。

舉例來說,在車用應用程式的 Session 中,初始化 CarConnection 並訂閱 LiveData 更新:

Kotlin

CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)

Java

new CarConnection(getCarContext()).getType().observe(this, this::onConnectionStateUpdated);

在觀察器中,您可以回應連線狀態變更:

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();
}

限制 API

不同車輛可能會一次向使用者顯示不同的 Item 例項。請使用 ConstraintManager 檢查執行階段的內容限制,並在範本中設定適當的項目數量。

請先從 CarContext 取得 ConstraintManager

Kotlin

val manager = carContext.getCarService(ConstraintManager::class.java)

Java

ConstraintManager manager = getCarContext().getCarService(ConstraintManager.class);

接著,您可以查詢擷取的 ConstraintManager 物件,瞭解相關內容限制。舉例來說,如要取得可在格狀檢視畫面中顯示的項目數量,請使用 CONTENT_LIMIT_TYPE_GRID 呼叫 getContentLimit

Kotlin

val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)

Java

int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);

新增登入流程

如果應用程式為使用者提供登入體驗,您可以使用 SignInTemplateLongMessageTemplate 等範本搭配 Car App API 級別 2 以上級別,來處理車輛車用運算主機上的應用程式登入作業。

如要建立 SignInTemplate,請定義 SignInMethod。Car App Library 目前支援下列登入方式:

舉例來說,如要實作用來收集使用者密碼的範本,請先建立 InputCallback 來處理及驗證使用者輸入內容:

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.
    }
};

必須為 InputSignInMethod Builder 指定 InputCallback

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();

最後,使用新的 InputSignInMethod 建立 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();

使用 AccountManager

設有驗證功能的 Android Automotive OS 應用程式都必須使用 AccountManager,原因如下:

  • 提供更優質的使用者體驗和簡便的帳戶管理功能:使用者可透過系統設定中的帳戶選單輕鬆管理所有帳戶 (包括登入及登出帳戶)。
  • 「訪客」體驗:由於車輛是共用裝置,因此原始設備製造商 (OEM) 可以在車輛上啟用訪客體驗,藉此禁止新增帳戶。

新增文字字串變化版本

不同大小的車輛螢幕顯示的文字數量可能不同。使用 Car App API 級別 2 及以上版本時,您可以指定文字字串的多個變化版本,以呈現最適合螢幕的大小。如要查看系統接受文字變化版本的位置,請尋找採用 CarText 的範本和元件。

您可以使用 CarText.Builder.addVariant() 方法將文字字串變體新增至 CarText

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();

接著,您可以使用這個 CarText (例如做為 GridItem 的主要文字)。

Kotlin

GridItem.Builder()
    .addTitle(itemTitle)
    ...
    .build()

Java

new GridItem.Builder()
    .addTitle(itemTitle)
    ...
    build();

按照偏好由高到低新增字串,例如從最長到最短。主機會根據車輛螢幕的可用空間大小,選擇適當的長度字串。

為資料列新增內嵌 CarIcon

您可以使用 CarIconSpan 新增文字內嵌圖示,讓應用程式的視覺吸引力更豐富。如要進一步瞭解如何建立這些 Span,請參閱 CarIconSpan.create 的說明文件。如需跨距文字樣式設定總覽,請參閱「使用 Span 的 Spantastic 文字樣式」。

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();
  
  

車輛硬體 API

從 Car App API 級別 3 開始,Car App Library 具備 API,可用於存取車輛屬性和感應器。

相關規定

如要搭配 Android Auto 使用 API,請先在 Android Auto 模組的 build.gradle 檔案中加入 androidx.car.app:app-projected 的依附元件。若是 Android Automotive OS,請將 androidx.car.app:app-automotive 的依附元件新增至 Android Automotive OS 模組的 build.gradle 檔案。

此外,您必須在 AndroidManifest.xml 檔案中宣告要求要使用的相關權限,以便要求要使用的車輛資料。請注意,使用者也必須授予這些權限。您可以在 Android Auto 和 Android Automotive OS 上使用相同程式碼,不必建立平台專屬的流程。但所需的權限並不相同。

車輛資訊

下表說明 CarInfo API 顯示的屬性,以及使用這些 API 所須要求的權限:

方法 屬性 Android Auto 權限 Android Automotive OS 權限
fetchModel 廠牌、型號、年份 android.car.permission.CAR_INFO
fetchEnergyProfile 電動車連接器類型、燃料類型 com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_INFO
addTollListener
removeTollListener
付費卡狀態、付費卡類型
addEnergyLevelListener
removeEnergyLevelListener
電池電量, 燃油量, 油量偏低, 剩餘電量 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
原始速度、螢幕速度 (顯示於車輛的儀表板螢幕上) com.google.android.gms.permission.CAR_SPEED android.car.permission.CAR_SPEED
android.car.permission.READ_CAR_DISPLAY_UNITS
addMileageListener
removeMileageListener
里程表距離 com.google.android.gms.permission.CAR_MILEAGE 這項資料不適用於 Android Automotive OS 和透過 Play 商店安裝的應用程式。

舉例來說,如要取得其餘範圍,請將 CarInfo 物件例項化,然後建立並註冊 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);

請勿假設車輛資料隨時可用。 如果您收到錯誤訊息,請查看所要求值的狀態,進一步瞭解無法擷取您要求資料的原因。如要瞭解完整的 CarInfo 類別定義,請參閱參考說明文件

車輛感應器

CarSensors 類別可讓您存取車輛的加速計、陀螺儀、指南針和位置資料。這些值的可用性可能因原始設備製造商 (OEM) 而異。加速計、陀螺儀和指南針的資料格式與 SensorManager API 相同。舉例來說,如要查看車輛方向,請按照下列步驟操作:

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);

如要存取車輛的位置資料,您也需要宣告並要求 android.permission.ACCESS_FINE_LOCATION 權限。

測試

如要在使用 Android Auto 上測試時模擬感應器資料,請參閱電腦版車用運算主機指南的感應器感應器設定章節。如要在 Android Automotive OS 上測試時模擬感應器資料,請參閱 Android Automotive OS 模擬器指南的「模擬器硬體狀態」一節。

CarAppService、工作階段和畫面生命週期

SessionScreen 類別會實作 LifecycleOwner 介面。當使用者與應用程式互動時,系統會叫用 SessionScreen 物件的生命週期回呼,如下圖所示。

CarAppService 和工作階段的生命週期

圖 1. Session 生命週期。

詳情請參閱 Session.getLifecycle 方法的說明文件。

畫面的生命週期

圖 2. Screen 生命週期。

詳情請參閱 Screen.getLifecycle 方法的說明文件。

透過車輛麥克風錄音

使用應用程式的 CarAppServiceCarAudioRecord API,即可授權應用程式存取使用者的車輛麥克風。使用者必須授予應用程式存取車輛麥克風的權限。應用程式可以記錄及處理使用者在應用程式中的輸入內容。

錄製權限

錄製任何音訊之前,您必須先在 AndroidManifest.xml 中宣告錄音權限,並要求使用者授予權限。

<manifest ...>
   ...
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   ...
</manifest>

你必須要求權限才能在執行階段錄影。如要進一步瞭解如何在車用應用程式中要求權限,請參閱「要求權限」一節。

錄音

使用者授予錄製權限後,您就可以錄製音訊並處理錄製內容。

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();
 

音訊焦點

透過車輛麥克風錄音時,請先取得音訊對焦,確保所有進行中的媒體均已停止。如果失去音訊焦點,請停止錄製。

以下範例說明如何取得音訊焦點:

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
 

測試程式庫

車輛專用 Android 測試程式庫提供輔助類別,可用來在測試環境中驗證應用程式的行為。舉例來說,SessionController 可讓您模擬連至主機的連線,並確認已建立並傳回正確的 ScreenTemplate

如需使用範例,請參閱範例

回報車輛專用 Android App Library 問題

如果您發現程式庫有問題,請使用 Google Issue Tracker 回報。在問題範本中,請務必填寫所有必要資訊。

建立新問題

提交新問題之前,請確認該問題是否列在程式庫的版本資訊或問題清單中。您可以在追蹤程式中按一下該問題的星號,訂閱該問題並投下一票。詳情請參閱訂閱問題一文。