使用儀表板 API (Android API) 在車輛的次要螢幕上顯示導航應用程式,包括 Google 地圖,例如儀表板上方向盤後方的螢幕。本頁面說明如何建立服務來控制次要螢幕,以及如何將服務與 CarService 整合,讓導航應用程式顯示使用者介面。
術語
本頁面使用下列術語。
CarManager 的例項,可讓外部應用程式在儀錶板上啟動活動,並在儀錶板準備好顯示活動時接收回呼。android:singleUser 屬性的 Android 服務。在任何時間點,Android 系統最多只能執行一個服務執行個體。必要條件
繼續操作前,請務必備妥下列項目:
- Android 開發環境。如要設定 Android 開發環境,請參閱「建構需求」。
- 下載 Android 原始碼。從 https://android.googlesource.com 的 pi-car-release 分支 (或更新版本) 取得最新版 Android 原始碼。
- 車用運算主機 (HU)。搭載 Android 9 以上版本的 Android 裝置。這個裝置必須有自己的螢幕,且能夠透過新版 Android 刷新螢幕。
- 「Instrument Cluster」是下列其中一項:
- 連接至 HU 的實體次要螢幕。裝置硬體和核心支援管理多個螢幕。
- 獨立裝置。透過網路連線連上 HU 的任何運算單元,能夠在自己的螢幕上接收及顯示影片串流。
- 模擬螢幕。開發期間,您可以使用下列任一模擬環境:
- 模擬的次要螢幕。如要在任何 AOSP Android 發行版本上啟用模擬的次要螢幕,請前往「設定」系統應用程式中的「開發人員選項」,然後選取「模擬次要螢幕」。這項設定等同於連接實體次要螢幕,但這類螢幕會疊加在主要螢幕上。
- 模擬儀表板。AAOS 隨附的 Android 模擬器提供選項,可透過 ClusterRenderingService 顯示儀表板。
整合架構
整合元件
整合儀表板 API 時,必須包含下列三個元件:
CarService- 導航應用程式
- OEM 儀表板服務

CarService
CarService 會在導航應用程式和車輛之間進行中介,確保任何時間都只有一個導航應用程式處於啟用狀態,且只有具備 android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL 權限的應用程式可以將資料傳送至車輛。
CarService 會啟動所有車輛專屬服務,並透過一系列管理工具提供這些服務的存取權。如要與服務互動,車輛中執行的應用程式可以存取這些管理工具。
如要實作儀表板叢集,汽車 OEM 必須建立 InstrumentClusterRendererService 的自訂實作項目,並更新 ClusterRenderingService。
在轉譯儀錶板時,CarService 會在啟動程序中讀取 ClusterRenderingService 的 InstrumentClusterRendererService 鍵,找出 InstrumentClusterService 的實作項目。在 AOSP 中,這個項目指向 Navigation State API 範例叢集實作的算繪服務:
<string name="instrumentClusterRendererService"> android.car.cluster/.ClusterRenderingService </string>
這個項目中提及的服務會初始化並繫結至 CarService。當 Google 地圖等導航應用程式要求 CarInstrumentClusterManager 時,CarService 會提供管理工具,從繫結的 InstrumentClusterRenderingService 更新儀表板狀態。(在本例中,「繫結」是指 Android 服務。)
儀表板服務
原始設備製造商必須建立 Android 套件 (APK),其中包含 ClusterRenderingService 的子類別。
這個類別有兩個用途:
- 提供 Android 和儀表板算繪裝置的介面 (本頁面的用途)。
- 接收及顯示導航狀態更新,例如即時路線導航指引。
為達成第一個目的,OEM 實作項目必須初始化用於在車輛座艙螢幕上算繪資訊的次要螢幕,並呼叫 InstrumentClusterRendererService.setClusterActivityOptions() 和 InstrumentClusterRendererService.setClusterActivityState() 方法,將這項資訊傳達給 CarService。InstrumentClusterRendererService
對於第二個函式,儀表板叢集服務必須提供 ClusterRenderingService 介面的實作項目,該介面會接收導覽狀態更新事件,這些事件會編碼為 eventType,而事件資料則會編碼為套件。
整合順序
下圖說明如何實作導覽狀態,以算繪更新:
在這張圖中,顏色代表的意義如下:
- 黃色。Android 平台提供的
CarService和CarNavigationStatusManager。詳情請參閱「車輛」和「CAR_NAVIGATION_SERVICE」。 - 青色。
InstrumentClusterRendererService。 - 紫色:由 Google 和第三方開發人員實作的導覽應用程式。
- 綠色。
CarAppFocusManager。詳情請參閱下方的「使用 CarAppFocusManager API」和「CarAppFocusManager」。
導覽狀態資訊流程如下:
CarService會初始化InstrumentClusterRenderingService。- 初始化期間,
InstrumentClusterRenderingService會使用下列項目更新CarService:- 儀表板顯示屬性,例如無遮蔽邊界 (稍後會詳細說明無遮蔽邊界)。
- 在儀錶板螢幕中啟動活動所需的活動選項。 詳情請參閱「ActivityOptions」。
- 導航應用程式 (例如 Android Automotive 版 Google 地圖,或任何具備必要權限的地圖應用程式):
- 使用 car-lib 中的 Car 類別取得
CarAppFocusManager。 - 在開始即時路線前,請呼叫
CarAppFocusManager.requestFocus(),將CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION做為appType參數傳遞。
- 使用 car-lib 中的 Car 類別取得
CarAppFocusManager會將這項要求傳達給CarService。 如果獲得授權,CarService會檢查導覽應用程式套件,並找出標示類別android.car.cluster.NAVIGATION的活動。- 如果找到,導覽應用程式會使用
InstrumentClusterRenderingService回報的ActivityOptions啟動活動,並將儀表板顯示屬性做為意圖中的額外項目。
整合 API
InstrumentClusterRenderingService 實作項目必須:
- 在 AndroidManifest.xml 中加入下列值,將其指定為單例服務。這麼做可確保即使在初始化和使用者切換期間,儀表板服務也只會執行單一副本:
android:singleUser="true" - 持有
BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE系統權限。這項權限可確保只有 Android 系統映像檔隨附的儀表板顯示服務,會繫結至CarService:<uses-permission android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"/>
實作 InstrumentClusterRenderingService
如要建構服務,請按照下列指示操作:
- 編寫從 ClusterRenderingService 擴充的類別,然後在
AndroidManifest.xml檔案中新增對應項目。這個類別會控制儀表板螢幕,並可 (選擇性) 算繪 Navigation State API 資料。 - 在
onCreate()期間,使用這項服務初始化與算繪硬體的通訊。選項包括:- 決定要用於儀表板的次要螢幕。
- 建立虛擬螢幕,讓 Instrument Cluster 應用程式算繪並將算繪的圖像傳輸至外部裝置 (使用視訊串流格式,例如 H.264)。
- 當上述顯示畫面準備就緒時,這項服務必須呼叫
InstrumentClusterRenderingService#setClusterActivityLaunchOptions(),定義要在儀表板上顯示活動的確切ActivityOptions。請使用下列參數:category.ClusterRenderingService。ActivityOptions.可用於在儀表板中啟動 Activity 的ActivityOptions例項。舉例來說,在 AOSP 的範例儀表板實作中:getService().setClusterActivityLaunchOptions( CATEGORY_NAVIGATION, ActivityOptions.makeBasic() .setLaunchDisplayId(displayId));
- 當儀表板準備好顯示活動時,這項服務必須叫用
InstrumentClusterRenderingService#setClusterActivityState()。請使用下列參數:categoryClusterRenderingService。state使用 ClusterRenderingService 產生的套件。請務必提供下列資料:visible指定儀表板叢集為可見狀態,並準備好顯示內容。unobscuredBounds矩形:定義儀表板顯示器內可安全顯示內容的區域。例如,撥號和儀表涵蓋的區域。
- 覆寫
Service#dump()方法,並回報有助於偵錯的狀態資訊 (詳情請參閱 dumpsys)。
InstrumentClusterRenderingService 實作範例
以下範例說明 InstrumentClusterRenderingService 實作方式,該實作會建立 VirtualDisplay,在遠端實體螢幕上呈現儀表板叢集內容。
或者,如果已知有可用的實體次要螢幕連接至 HU,這個代碼可以傳遞該螢幕的 displayId。
/** * Sample {@link InstrumentClusterRenderingService} implementation */ public class SampleClusterServiceImpl extends InstrumentClusterRenderingService { // Used to retrieve or create displays private final DisplayManager mDisplayManager; // Unique identifier for the display to be used for instrument // cluster private final String mUniqueId = UUID.randomUUID().toString(); // Format of the instrument cluster display private static final int DISPLAY_WIDTH = 1280; private static final int DISPLAY_HEIGHT = 720; private static final int DISPLAY_DPI = 320; // Area not covered by instruments private static final int DISPLAY_UNOBSCURED_LEFT = 40; private static final int DISPLAY_UNOBSCURED_TOP = 0; private static final int DISPLAY_UNOBSCURED_RIGHT = 1200; private static final int DISPLAY_UNOBSCURED_BOTTOM = 680; @Override public void onCreate() { super.onCreate(); // Create a virtual display to render instrument cluster activities on mDisplayManager = getSystemService(DisplayManager.class); VirtualDisplay display = mDisplayManager.createVirtualDisplay( mUniqueId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_DPI, null, 0 /* flags */, null, null); // Do any additional initialization (e.g.: start a video stream // based on this virtual display to present activities on a remote // display). onDisplayReady(display.getDisplay()); } private void onDisplayReady(Display display) { // Report activity options that should be used to launch activities on // the instrument cluster. String category = CarInstrumentClusterManager.CATEGORY_NAVIGATION; ActionOptions options = ActivityOptions.makeBasic() .setLaunchDisplayId(display.getDisplayId()); setClusterActivityOptions(category, options); // Report instrument cluster state. Rect unobscuredBounds = new Rect(DISPLAY_UNOBSCURED_LEFT, DISPLAY_UNOBSCURED_TOP, DISPLAY_UNOBSCURED_RIGHT, DISPLAY_UNOBSCURED_BOTTOM); boolean visible = true; ClusterActivityState state = ClusterActivityState.create(visible, unobscuredBounds); setClusterActivityState(category, options); } }
使用 CarAppFocusManager API
CarAppFocusManager API 提供名為 getAppTypeOwner() 的方法,可讓原始設備製造商 (OEM) 編寫的叢集服務隨時瞭解哪個導航應用程式擁有瀏覽焦點。原始設備製造商 (OEM) 可以使用現有的 CarAppFocusManager#addFocusListener() 方法,然後使用 getAppTypeOwner() 瞭解哪個應用程式具有焦點。有了這項資訊,原始設備製造商可以:
- 將儀表板顯示的活動切換為持有焦點的導航應用程式提供的儀表板活動。
- 可偵測焦點導航應用程式是否具有叢集活動。如果聚焦的導航應用程式沒有叢集活動 (或這類活動已停用),OEM 可以將這個信號傳送至車輛 DIM,這樣一來,叢集的導航方面就會完全略過。
使用 CarAppFocusManager 設定及聆聽目前應用程式的焦點,例如即時導航或語音指令。通常系統中只會主動執行 (或聚焦) 這類應用程式的一個執行個體。
使用 CarAppFocusManager#addFocusListener(..) 方法監聽應用程式焦點變更:
import android.car.CarAppFocusManager; ... Car car = Car.createCar(this); mAppFocusManager = (CarAppFocusManager)car.getCarManager(Car.APP_FOCUS_SERVICE); mAppFocusManager.addFocusListener(this, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); ... public void onAppFocusChanged(int appType, boolean active) { // Use the CarAppFocusManager#getAppTypeOwner(appType) method call // to retrieve a list of active package names }
使用 CarAppFocusManager#getAppTypeOwner(..) 方法,擷取目前擁有特定應用程式類型 (目前為焦點) 的套件名稱。如果目前的擁有者使用 android:sharedUserId 功能,這個方法可能會傳回多個套件名稱。
import android.car.CarAppFocusManager; ... Car car = Car.createCar(this); mAppFocusManager = (CarAppFocusManager)car.getCarManager(Car.APP_FOCUS_SERVICE); List<String> focusOwnerPackageNames = mAppFocusManager.getAppTypeOwner( CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); if (focusOwnerPackageNames == null || focusOwnerPackageNames.isEmpty()) { // No Navigation app has focus // OEM may choose to show their default cluster view } else { // focusOwnerPackageNames // Use the PackageManager to retrieve the cluster activity for the package(s) // returned in focusOwnerPackageNames } ...
找出範本應用程式
對於使用 Car App Library 的範本式導航應用程式,CarAppFocusManager#getAppTypeOwner() 會傳回主機的套件名稱 (例如 com.google.android.apps.automotive.templates.host),因為主機會代表用戶端應用程式保留系統焦點。
如要識別導覽用戶端應用程式,原始設備製造商可以從 CarNavigationStatusManager 傳送的導覽狀態軟體包中擷取套件名稱。套件名稱會儲存在 NavigationRenderer#onNavigationStateChanged(Bundle) 收到的套件中,位於 active_app_package_name 鍵下方:
// In your NavigationRenderer implementation @Override public void onNavigationStateChanged(Bundle bundle) { if (bundle.containsKey("active_app_package_name")) { String activeAppPackage = bundle.getString("active_app_package_name"); // Use the package name to identify the navigating app (e.g., com.waze) } }
附錄:使用範例應用程式
Android 開放原始碼計畫 (AOSP) 提供實作 Navigation State API 的範例應用程式。
如要執行這個範例應用程式,請按照下列步驟操作:
- 在支援的 HU 上建構及刷新 Android Auto。請使用裝置專用的 Android 建構和刷新指示。如需指示,請參閱「使用參考板」。
- 將實體次要螢幕連接至 HU (如支援),或開啟虛擬次要 HU:
- 在「設定」應用程式中選取「開發人員模式」。
- 依序前往「設定」>「系統」>「進階」>「開發人員選項」>「模擬第二螢幕」。
- 重新啟動 HU
- 啟動 KitchenSink 應用程式:
- 開啟抽屜。
- 前往「Inst. Cluster」。
- 按一下「開始中繼資料」。
KitchenSink 會要求 NAVIGATION 焦點,指示 DirectRenderingCluster 服務在儀表板上顯示模擬的使用者介面。