Android Automotive 認為語音是確保行車安全互動的關鍵要素,也是使用者在行車時與 Android Automotive OS 互動的最安全方式之一。因此,我們擴充了 Android 語音助理 API (包括 VoiceInteractionSession
),讓語音助理可為使用者執行開車時難以完成的任務。
當使用者與訊息通知互動時,輕觸朗讀功能可讓語音助理代替使用者朗讀及回覆簡訊。如要提供這項功能,您可以將語音助理與 CarVoiceInteractionSession
整合。
在 Automotive 中,發布至通知中心的通知會標示為 INBOX
或 INBOX_IN_GROUP
(例如簡訊),並包含播放按鈕。使用者可以點選「Play」,讓所選語音助理朗讀通知內容,並視需要透過語音回覆。
圖 1. 含有「輕觸即可朗讀」按鈕的通知。
與 CarVoiceInteractionSession 整合
接下來的章節將說明如何將語音助理與 CarVoiceInteractionSession
整合。
支援語音互動
提供車用語音互動服務的應用程式必須整合現有的 Android 語音互動功能。如需更多資訊,請參閱 Android 版 Google 助理 (VoiceInteractionSession
除外)。雖然所有語音互動 API 元素都與行動裝置上實作的方式相同,但 CarVoiceInteractionSession
(請參閱「實作 CarVoiceInteractionSession」一文) 會取代 VoiceInteractionSession
。如需詳細資訊,請參閱下列頁面:
實作 CarVoiceInteractionSession
CarVoiceInteractionSession
會公開 API,讓您啟用語音助理朗讀簡訊,然後代表使用者回覆這些訊息。
CarVoiceInteractionSession
和 VoiceInteractionSession
類別之間的主要差異在於,CarVoiceInteractionSession
會在 onShow
中傳入動作,因此語音助理可以在 CarVoiceInteractionSession
開始工作階段時,立即偵測使用者要求的內容。下表列出每個類別的 onShow
參數:
CarVoiceInteractionSession | VoiceInteractionSession |
---|---|
onShow 會使用以下三個參數:
|
onShow 會採用以下兩個參數:
|
Android 10 的變更
從 Android 10 開始,平台會呼叫 VoiceInteractionService.onGetSupportedVoiceActions
,以偵測支援哪些動作。語音助理會覆寫並實作 VoiceInteractionService.onGetSupportedVoiceActions
,如以下範例所示:
public class MyInteractionService extends VoiceInteractionService { private static final ListSUPPORTED_VOICE_ACTIONS = Arrays.asList( CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION); @Override public Set onGetSupportedVoiceActions(@NonNull Set voiceActions) { Set result = new HashSet<>(voiceActions); result.retainAll(SUPPORTED_VOICE_ACTIONS); return result; } }
下表說明有效的動作。如要進一步瞭解每項動作,請參閱序列圖表。
動作 | 預期酬載 | 預期的語音互動動作 |
---|---|---|
VOICE_ACTION_READ_NOTIFICATION |
向使用者朗讀訊息,然後在成功讀取訊息時觸發「Mark as Read」待處理意圖。視需要提示使用者回覆。 | |
VOICE_ACTION_REPLY_NOTIFICATION |
可透過鍵盤分割的物件。KEY_NOTIFICATION
對應至 StatusBarNotification 。需要 android.permission.BIND_NOTIFICATION_LISTENER_SERVICE 。 |
提示使用者說明回覆訊息,將回覆訊息輸入待處理意圖的 RemoteInputReply ,然後觸發待處理意圖。 |
VOICE_ACTION_HANDLE_EXCEPTION |
含有鍵的字串。KEY_EXCEPTION
對應至 ExceptionValue (請參閱「例外值」)。KEY_FALLBACK_ASSISTANT_ENABLED 會對應至布林值。如果值為 true ,表示可處理使用者要求的備用助理已停用。 |
例外狀況說明文件會定義應對例外狀況採取的預期動作。 |
例外值
EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING
會向語音助理指出缺少 Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE
權限,並要求從使用者取得這項權限。
要求通知事件監聽器權限
如果預設語音助理沒有通知事件監聽器權限,平台的 FallbackAssistant
(如果由車輛製造商啟用) 可能會在語音助理收到要求權限的通知前,朗讀訊息。如要判斷是否已啟用 FallbackAssistant
並讀取訊息,語音助理應檢查酬載中的 KEY_FALLBACK_ASSISTANT_ENABLED
布林值。
平台建議語音助理針對此權限要求次數加入頻率限制邏輯。這樣做可尊重使用者不想授予語音助理這項權限,並希望FallbackAssistant
朗讀簡訊的偏好設定。每次使用者按下訊息通知中的「播放」按鈕時,系統都會提示使用者授權,這可能會造成負面使用者體驗。平台不會代表語音助理強制執行頻率限制。
在要求通知事件監聽器權限時,語音助理應使用 CarUxRestrictionsManager
判斷使用者是否已停車或正在行駛。如果使用者正在開車,語音助理會顯示通知,提供授權方式的操作說明。這樣做有助於提醒使用者在安全無虞的情況下授予權限。
使用 StatusBarNotification
如「通知使用者訊息」一文所述,透過「讀取」和「回覆」語音指令傳入的 StatusBarNotification
一律會出現在與汽車相容的訊息通知中。雖然部分通知可能沒有「回覆待處理」意圖,但都會包含「標示為已讀」待處理意圖。
如要簡化與通知的互動,請使用 NotificationPayloadHandler
,這個類別提供方法,可從通知中擷取訊息,並將回覆訊息寫入通知的適當待處理意圖。語音助理讀出訊息後,必須觸發「Mark as Read」意圖。
滿足輕觸閱讀先決條件
當使用者觸發語音操作來讀取及回覆訊息時,系統只會通知預設語音助理的 VoiceInteractionSession
。如上所述,這個預設語音助理也必須具備通知事件監聽器權限。
流程圖
以下圖表顯示 CarVoiceInteractionSession actions
的邏輯流程:
圖 2. VOICE_ACTION_READ_NOTIFICATION 的流程圖。
在圖 3 的情況下,建議應用程式對權限要求設定頻率限制:
圖 3. VOICE_ACTION_REPLY_NOTIFICATION 的流程圖。
圖 4. VOICE_ACTION_HANDLE_EXCEPTION 的流程圖。
讀取應用程式名稱
如果您希望語音助理在朗讀訊息時大聲念出訊息應用程式的名稱 (例如「Hangouts 的 Sam 說...」),請建立下列程式碼範例所示的函式,確保助理讀出正確的名稱:
@Nullable String getMessageApplicationName(Context context, StatusBarNotification statusBarNotification) { ApplicationInfo info = getApplicationInfo(context, statusBarNotification.getPackageName()); if (info == null) return null; Notification notification = statusBarNotification.getNotification(); // Sometimes system packages will post on behalf of other apps, so check this // field for a system app notification. if (isSystemApp(info) && notification.extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) { return notification.extras.getString(Notification.EXTRA_SUBSTITUTE_APP_NAME); } else { PackageManager pm = context.getPackageManager(); return String.valueOf(pm.getApplicationLabel(info)); } } @Nullable ApplicationInfo getApplicationInfo(Context context, String packageName) { final PackageManager pm = context.getPackageManager(); ApplicationInfo info; try { info = pm.getApplicationInfo(packageName, 0); } catch (PackageManager.NameNotFoundException e) { return null; } return info; } boolean isSystemApp(ApplicationInfo info) { return (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; }