語音助理觸控朗讀

Android Automotive 認為,語音是消費者 確保使用者的安全 在行車期間與 Android Automotive OS 互動。因此,我們擴充了 Android 語音助理 API (包括 VoiceInteractionSession) 讓語音助理可以為使用者執行工作 難以在開車時做到的事項

輕觸即可朗讀功能可讓語音助理在開啟及回覆簡訊時朗讀內容 (代表使用者與訊息通知互動時)。為了提供 ,您可以將語音助理與 CarVoiceInteractionSession

在 Automotive 中,系統發現發布至通知中心的通知 本裝置:INBOXINBOX_IN_GROUP (例如簡訊) 包含 「播放」按鈕。使用者可以按一下「播放」選取 語音助理會朗讀通知內容,您也可以選擇透過語音回覆。

輕觸閱讀通知

圖 1. 附有「播放」按鈕的輕觸閱讀通知。

與 CarVoiceInteractionSession 整合

以下各節將說明如何整合語音助理與語音助理。 CarVoiceInteractionSession

支援語音互動

提供車輛語音互動服務的應用程式必須 整合現有的 Android 語音互動功能詳情請參閱「Android 版 Google 助理」一文 (VoiceInteractionSession 除外)。雖然所有語音互動 API 都適用 元素與在行動裝置上實作 CarVoiceInteractionSession 的效果相同 (詳情請參閱「實作 CarVoiceInteractionSession」一文) VoiceInteractionSession。如需詳細資訊,請參閱以下頁面:

實作 CarVoiceInteractionSession

CarVoiceInteractionSession 將會揭露 API,您可以使用這些 API 讓語音助理朗讀文字訊息, 代使用者回覆這些訊息。

CarVoiceInteractionSession 和 這就是 VoiceInteractionSession 類別 CarVoiceInteractionSession的「onShow」動作票證 以便語音助理立即偵測使用者要求的背景資訊 CarVoiceInteractionSession 會啟動工作階段。onShow 的參數 請參閱下表:

CarVoiceInteractionSession 語音互動工作階段
onShow 會使用以下三個參數:
  • args
  • showFlags
  • actions
onShow 會使用以下兩個參數:
  • args
  • showFlags

Android 10 的異動

從 Android 10 開始,平台會呼叫 VoiceInteractionService.onGetSupportedVoiceActions 偵測支援的動作語音助理會覆寫和 實作 VoiceInteractionService.onGetSupportedVoiceActions, 如以下範例所示:

public class MyInteractionService extends VoiceInteractionService {
    private static final List SUPPORTED_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 朗讀訊息給使用者,然後啟動標示為「已讀」的待處理 意圖回應您可以選擇提示 表示回覆。
VOICE_ACTION_REPLY_NOTIFICATION 含鍵的 Parcelable。
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 讀取意圖

「輕觸閱讀」先決條件符合要求

預設語音只有 VoiceInteractionSession 使用者觸發要朗讀的語音和訊息時,助理會收到通知 回覆訊息。如上所述,必須一併讓這個預設的語音助理 具備通知事件監聽器權限

序列圖

下圖顯示 CarVoiceInteractionSession actions 的邏輯流程:

VOICE_ACTION_READ_NOTIFICATION

圖 2. VOICE_ACTION_READ_NOTIFICATION 的循序圖。

以圖 3 為例,建議應用程式權限要求的頻率限制:

VOICE_ACTION_REPLY_NOTIFICATION

圖 3. VOICE_ACTION_REPLY_NOTIFICATION 的序列圖。

VOICE_ACTION_HANDLE_EXCEPTION

圖 4. VOICE_ACTION_HANDLE_EXCEPTION 的序列圖。

讀取應用程式名稱

如果希望語音助理在練習期間 訊息朗讀 (例如,「來自 Hangouts 的 Sam 表示了...」) 建立函式,如下所示 在以下程式碼範例中,確保 Google 助理讀取的是正確名稱:

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