Tính năng Nhấn để đọc của Trợ lý thoại

Android Automotive coi giọng nói là một thành phần quan trọng để tương tác an toàn khi lái xe và là một trong những cách an toàn nhất để người dùng tương tác với Android Automotive OS khi lái xe. Do đó, chúng tôi đã mở rộng các API trợ lý thoại của Android (bao gồm cả VoiceInteractionSession) để cho phép trợ lý thoại thực hiện các tác vụ mà người dùng khó có thể hoàn thành khi đang lái xe.

Tính năng Nhấn để đọc cho phép trợ lý thoại đọc và trả lời tin nhắn văn bản thay mặt cho người dùng khi người dùng tương tác với thông báo tin nhắn. Để cung cấp chức năng này, bạn có thể tích hợp trợ lý thoại với CarVoiceInteractionSession.

Trong Automotive, các thông báo được đăng lên Trung tâm thông báo được xác định là INBOX hoặc INBOX_IN_GROUP (ví dụ: tin nhắn SMS) sẽ có nút Phát. Người dùng có thể nhấp vào Phát để yêu cầu trợ lý thoại đã chọn đọc to thông báo và trả lời bằng giọng nói (không bắt buộc).

Thông báo nhấn để đọc

Hình 1. Thông báo Nhấn để đọc có nút Phát.

Tích hợp với CarVoiceInteractionSession

Các phần tiếp theo mô tả cách tích hợp trợ lý thoại với CarVoiceInteractionSession.

Hỗ trợ tương tác bằng giọng nói

Ứng dụng cung cấp dịch vụ tương tác bằng giọng nói trên ô tô phải tích hợp với các tính năng tương tác bằng giọng nói hiện có trên Android. Để tìm hiểu thêm, hãy xem phần Trợ lý Google dành cho Android (ngoại trừ VoiceInteractionSession). Mặc dù tất cả các phần tử API tương tác bằng giọng nói vẫn giống như được triển khai trên thiết bị di động, nhưng CarVoiceInteractionSession (mô tả trong phần Triển khai CarVoiceInteractionSession) sẽ thay thế VoiceInteractionSession. Để biết thêm thông tin, hãy xem các trang sau:

Triển khai CarVoiceInteractionSession

CarVoiceInteractionSession hiển thị các API mà bạn có thể sử dụng để cho phép trợ lý giọng nói đọc to tin nhắn văn bản, sau đó thay mặt người dùng trả lời các tin nhắn đó.

Điểm khác biệt chính giữa lớp CarVoiceInteractionSessionVoiceInteractionSessionCarVoiceInteractionSession truyền hành động trong onShow để trợ lý giọng nói có thể phát hiện ngữ cảnh của yêu cầu của người dùng ngay khi CarVoiceInteractionSession bắt đầu một phiên. Các tham số cho onShow cho từng lớp được liệt kê trong bảng sau:

CarVoiceInteractionSession VoiceInteractionSession
onShow nhận 3 tham số sau:
  • args
  • showFlags
  • actions
onShow nhận hai tham số sau:
  • args
  • showFlags

Các thay đổi trong Android 10

Kể từ Android 10, nền tảng sẽ gọi VoiceInteractionService.onGetSupportedVoiceActions để phát hiện những thao tác nào được hỗ trợ. Trợ lý thoại ghi đè và triển khai VoiceInteractionService.onGetSupportedVoiceActions, như trong ví dụ sau:

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

Các thao tác hợp lệ được mô tả trong bảng sau. Để biết thông tin chi tiết về từng hành động, hãy xem phần Sơ đồ trình tự.

Thao tác Trọng tải dự kiến Hành động tương tác bằng giọng nói dự kiến
VOICE_ACTION_READ_NOTIFICATION Đọc to tin nhắn cho người dùng, sau đó kích hoạt ý định Đánh dấu là Đã đọc đang chờ xử lý khi đọc xong tin nhắn. Bạn có thể nhắc người dùng phản hồi.
VOICE_ACTION_REPLY_NOTIFICATION Có thể phân phối bằng khoá.
KEY_NOTIFICATION liên kết đến StatusBarNotification.
Yêu cầu android.permission.BIND_NOTIFICATION_LISTENER_SERVICE.
Nhắc người dùng nêu thông báo trả lời, nhập thông báo trả lời vào RemoteInputReply của ý định đang chờ xử lý, sau đó kích hoạt ý định đang chờ xử lý.
VOICE_ACTION_HANDLE_EXCEPTION Chuỗi có khoá.
KEY_EXCEPTION ánh xạ đến ExceptionValue (được mô tả trong phần Giá trị ngoại lệ).
KEY_FALLBACK_ASSISTANT_ENABLED ánh xạ đến một giá trị Boolean. Nếu giá trị là true, thì trợ lý dự phòng có thể xử lý yêu cầu của người dùng đã bị tắt.
Hành động dự kiến cần thực hiện đối với ngoại lệ được xác định trong tài liệu về ngoại lệ.

Giá trị ngoại lệ

EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING cho trợ lý thoại biết rằng trợ lý này thiếu quyền Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE và cần người dùng cấp quyền này.

Yêu cầu cấp quyền cho trình nghe thông báo

Nếu trợ lý giọng nói mặc định không có quyền trình nghe thông báo, thì FallbackAssistant của nền tảng (nếu nhà sản xuất ô tô bật) có thể đọc to thông báo trước khi trợ lý giọng nói được thông báo để yêu cầu quyền. Để xác định xem FallbackAssistant có được bật và đọc thông báo hay không, trợ lý thoại phải kiểm tra giá trị Boolean KEY_FALLBACK_ASSISTANT_ENABLED trong tải trọng.

Nền tảng này đề xuất trợ lý thoại thêm logic giới hạn tốc độ cho số lần yêu cầu quyền này. Việc này tôn trọng người dùng không muốn cấp quyền này cho trợ lý thoại và muốn FallbackAssistant đọc to tin nhắn văn bản. Việc nhắc người dùng cấp quyền mỗi khi người dùng nhấn Phát trên thông báo tin nhắn có thể gây ra trải nghiệm không tốt cho người dùng. Nền tảng này không thay mặt cho trợ lý thoại áp đặt giới hạn tốc độ.

Khi yêu cầu quyền trình nghe thông báo, trợ lý thoại phải sử dụng CarUxRestrictionsManager để xác định xem người dùng đang đỗ xe hay đang lái xe. Nếu người dùng đang lái xe, trợ lý thoại sẽ hiển thị thông báo hướng dẫn cách cấp quyền. Việc này giúp (và nhắc) người dùng cấp quyền khi an toàn hơn.

Làm việc với StatusBarNotification

StatusBarNotification được truyền vào bằng thao tác bằng giọng nói Đọc và Trả lời luôn ở dạng thông báo nhắn tin tương thích với ô tô như mô tả trong phần Thông báo cho người dùng về tin nhắn. Mặc dù một số thông báo có thể không có ý định Trả lời đang chờ xử lý, nhưng tất cả đều có ý định Đánh dấu là đã đọc đang chờ xử lý.

Để đơn giản hoá hoạt động tương tác với thông báo, hãy sử dụng NotificationPayloadHandler. Phương thức này cung cấp các phương thức để trích xuất thông báo từ thông báo và ghi thông báo trả lời vào ý định đang chờ xử lý thích hợp của thông báo. Sau khi đọc tin nhắn, trợ lý thoại phải kích hoạt ý định Đánh dấu là Đã đọc.

Đáp ứng các điều kiện tiên quyết của tính năng Nhấn để đọc

Chỉ VoiceInteractionSession của trợ lý thoại mặc định mới được thông báo khi người dùng kích hoạt thao tác bằng giọng nói để đọc và trả lời tin nhắn. Như đã đề cập ở trên, trợ lý thoại mặc định này cũng phải có quyền trình nghe thông báo.

Sơ đồ trình tự

Các hình này cho thấy luồng logic của CarVoiceInteractionSession actions:

VOICE_ACTION_READ_NOTIFICATION

Hình 2. Sơ đồ trình tự cho VOICE_ACTION_READ_NOTIFICATION.

Trong trường hợp của Hình 3, bạn nên áp dụng hạn mức số lượng yêu cầu cấp quyền cho ứng dụng:

VOICE_ACTION_REPLY_NOTIFICATION

Hình 3. Sơ đồ trình tự cho VOICE_ACTION_REPLY_NOTIFICATION.

VOICE_ACTION_HANDLE_EXCEPTION

Hình 4. Sơ đồ trình tự cho VOICE_ACTION_HANDLE_EXCEPTION.

Đọc tên ứng dụng

Nếu bạn muốn trợ lý thoại đọc to tên ứng dụng nhắn tin trong quá trình đọc tin nhắn (ví dụ: "Sam từ Hangouts nói..."), hãy tạo một hàm như trong ví dụ mã sau để đảm bảo trợ lý đọc đúng tên:

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