使用車輛專用 Android App Library

車輛專用 Android App Library 可讓您在車上提供導航、搜尋點 (POI) 和物聯網 (IOT) 應用程式。方法是提供一組符合駕駛人分心等級標準設計的範本,並妥善處理各種車輛螢幕因素和輸入模式等細節。

本指南將概略說明程式庫的主要功能與概念,並逐步引導您設定簡易應用程式。如需完整的逐步操作說明,請參閱「瞭解車輛應用程式程式庫基礎知識程式碼研究室」。

事前準備

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

重要詞彙與概念

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

Session 是一種抽象類別,應用程式必須使用 CarAppService.onCreateSession 實作並傳回。可做為在車輛螢幕上顯示資訊的進入點。具有lifecycle,用於告知應用程式在車輛螢幕上的目前狀態,例如應用程式顯示或隱藏時。

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>

車輛應用程式 API 級別

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

AndroidManifest.xml 檔案中宣告應用程式支援的最低 Car App 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 例項,代表車輛螢幕中要顯示的使用者介面狀態。

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

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 類別是 SessionScreen 執行個體可存取的 ContextWrapper 子類別。這個程式庫可讓您存取車輛服務,例如用於管理螢幕堆疊ScreenManager;用於一般應用程式相關功能的 AppManager,例如存取 Surface 物件以繪製導航應用程式的地圖;以及 NavigationManager 搭配 NavigationManager 使用 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() 方法。

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

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

透過意圖啟動車輛應用程式

您可以呼叫 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,主機會將配額重設為三,因為應用程式已經反向操作兩個範本。

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

重設作業

某些範本具有特殊語意,代表工作結束。舉例來說,NavigationTemplate 是預期會留在畫面中的檢視區塊,並且根據使用者的使用情況更新即時路線指示重新整理。達到其中一個範本時,主機會重設範本配額,視為該範本會視為新工作的第一步。讓應用程式開始新的工作。 請參閱個別範本的說明文件,瞭解哪些範本會在主機上重設。

如果主機收到從通知動作或啟動器啟動應用程式的意圖,系統也會重設配額。這項機制可讓應用程式從通知啟動新的工作流程,即使應用程式已繫結且在前景中,仍會保持不變。

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

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

新增登入流程

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

如要建立 SignInTemplate,請定義 SignInMethod。車輛應用程式程式庫目前支援下列登入方法:

舉例來說,如要實作會收集使用者密碼的範本,請先建立 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 BuilderInputCallback

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 文字樣式的運作方式總覽,請參閱「使用 Span 中的文字樣式設定」。

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 API

從 Car App API 級別 3 開始,車輛應用程式程式庫含有可用來存取車輛屬性和感應器的 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

下表說明 CarInfo 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

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 模擬器指南的「Emulate 硬體狀態」一節。

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 回報。請務必在問題範本中填寫所有必要資訊。

建立新問題

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