Hỗ trợ bàn phím hình ảnh

Người dùng thường muốn giao tiếp bằng biểu tượng cảm xúc, hình dán và các loại nội dung đa dạng thức khác. Trong các phiên bản Android trước, bàn phím mềm (còn gọi là trình chỉnh sửa phương thức nhập hoặc IME) chỉ có thể gửi biểu tượng cảm xúc Unicode đến các ứng dụng. Đối với nội dung đa dạng thức, ứng dụng đã tạo các API dành riêng cho ứng dụng mà không thể dùng trong các ứng dụng khác hoặc đã sử dụng các giải pháp như gửi hình ảnh thông qua thao tác chia sẻ đơn giản hoặc bảng nhớ tạm.

Hình ảnh cho thấy bàn phím hỗ trợ tính năng tìm kiếm bằng hình ảnh
Hình 1. Ví dụ về tính năng hỗ trợ bàn phím hình ảnh.

Kể từ Android 7.1 (API cấp 25), SDK Android bao gồm Commit Content API, cung cấp một cách thức chung để IME gửi hình ảnh và nội dung đa dạng thức khác trực tiếp tới trình chỉnh sửa văn bản trong ứng dụng. API cũng có trong Thư viện hỗ trợ v13 kể từ bản sửa đổi 25.0.0. Bạn nên sử dụng Thư viện hỗ trợ vì thư viện này chứa các phương thức trợ giúp giúp đơn giản hoá quá trình triển khai.

Với API này, bạn có thể tạo các ứng dụng nhắn tin chấp nhận nội dung đa dạng thức từ mọi bàn phím, cũng như bàn phím có thể gửi nội dung đa dạng thức đến bất kỳ ứng dụng nào. Bàn phím Google và các ứng dụng như Tin nhắn của Google hỗ trợ Commit Content API trong Android 7.1, như minh hoạ trong hình 1.

Tài liệu này trình bày cách triển khai Commit Content API trong cả IME và ứng dụng.

Cách hoạt động

Tính năng chèn hình ảnh trên bàn phím cần có sự tham gia của IME và ứng dụng. Trình tự sau đây mô tả từng bước trong quy trình chèn hình ảnh:

  1. Khi người dùng nhấn vào một EditText, trình chỉnh sửa sẽ gửi danh sách các loại nội dung MIME được chấp nhận trong EditorInfo.contentMimeTypes.

  2. IME đọc danh sách các loại được hỗ trợ và hiển thị nội dung trong bàn phím mềm mà trình chỉnh sửa có thể chấp nhận.

  3. Khi người dùng chọn một hình ảnh, IME sẽ gọi commitContent() và gửi InputContentInfo đến trình chỉnh sửa. Lệnh gọi commitContent() tương tự như lệnh gọi commitText() nhưng dành cho nội dung đa dạng thức. InputContentInfo chứa URI xác định nội dung trong một trình cung cấp nội dung.

Quá trình này được mô tả trong hình 2:

Hình ảnh hiển thị trình tự từ Application đến IME và quay lại Application
Hình 2. Ứng dụng IME cho quy trình xử lý ứng dụng.

Thêm tính năng hỗ trợ hình ảnh vào ứng dụng

Để chấp nhận nội dung đa dạng thức từ IME, ứng dụng phải cho IME biết loại nội dung nào chấp nhận và chỉ định phương thức gọi lại được thực thi khi nhận được nội dung. Ví dụ sau đây minh hoạ cách tạo EditText chấp nhận hình ảnh PNG:

Kotlin

var editText: EditText = object : EditText(this) {
    override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
        var ic = super.onCreateInputConnection(outAttrs)
        EditorInfoCompat.setContentMimeTypes(outAttrs, arrayOf("image/png"))
        val mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this)
        if (mimeTypes != null) {
            EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes)
            ic = InputConnectionCompat.createWrapper(this, ic, outAttrs)
        }
        return ic
    }
}

Java

EditText editText = new EditText(this) {
    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        InputConnection ic = super.onCreateInputConnection(outAttrs);
        EditorInfoCompat.setContentMimeTypes(outAttrs, new String[]{"image/png"});
        String[] mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this);
        if (mimeTypes != null) {
            EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes);
            ic = InputConnectionCompat.createWrapper(this, ic, outAttrs);
        }
        return ic;
    }
};

Sau đây là nội dung giải thích thêm:

  • Ví dụ này sử dụng Thư viện hỗ trợ, vì vậy, sẽ có một số tệp tham chiếu đến android.support.v13.view.inputmethod thay vì android.view.inputmethod.

  • Ví dụ này sẽ tạo một EditText và ghi đè phương thức onCreateInputConnection(EditorInfo) để sửa đổi InputConnection. InputConnection là kênh liên lạc giữa IME và ứng dụng đang nhận dữ liệu đầu vào.

  • Lệnh gọi super.onCreateInputConnection() duy trì hành vi tích hợp sẵn (gửi và nhận văn bản) đồng thời cung cấp cho bạn thông tin tham chiếu đến InputConnection.

  • setContentMimeTypes() thêm danh sách các loại MIME được hỗ trợ vào EditorInfo. Hãy gọi super.onCreateInputConnection() trước setContentMimeTypes().

  • callback được thực thi bất cứ khi nào IME gửi nội dung. Phương thức onCommitContent() có tham chiếu đến InputContentInfoCompat, chứa một URI nội dung.

    • Yêu cầu và huỷ bỏ quyền nếu ứng dụng của bạn đang chạy trên API cấp 25 trở lên và cờ INPUT_CONTENT_GRANT_READ_URI_PERMISSION là do IME đặt. Nếu không, bạn đã có quyền truy cập vào URI nội dung vì yêu cầu này được IME cấp hoặc vì trình cung cấp nội dung không hạn chế quyền truy cập. Để biết thêm thông tin, hãy xem phần Thêm tính năng hỗ trợ hình ảnh vào IME.
  • createWrapper() sẽ gói InputConnection, EditorInfo đã sửa đổi và lệnh gọi lại vào một InputConnection mới rồi trả về đối tượng đó.

Sau đây là các phương pháp được đề xuất:

  • Những trình chỉnh sửa không hỗ trợ nội dung đa dạng thức sẽ không gọi hàm setContentMimeTypes() và họ để EditorInfo.contentMimeTypes được đặt thành null.

  • Trình chỉnh sửa sẽ bỏ qua nội dung nếu loại MIME được chỉ định trong InputContentInfo không khớp với bất kỳ loại nào mà họ chấp nhận.

  • Nội dung đa dạng thức không ảnh hưởng và không bị ảnh hưởng bởi vị trí của con trỏ văn bản. Trình chỉnh sửa có thể bỏ qua vị trí con trỏ khi làm việc với nội dung.

  • Trong phương thức OnCommitContentListener.onCommitContent() của trình chỉnh sửa, bạn có thể trả về true không đồng bộ, ngay cả trước khi tải nội dung.

  • Không giống như văn bản (có thể chỉnh sửa được trong IME trước khi gửi), nội dung đa dạng thức sẽ được xác nhận ngay lập tức. Nếu bạn muốn cho phép người dùng chỉnh sửa hoặc xoá nội dung, hãy tự triển khai logic.

Để kiểm thử ứng dụng, hãy đảm bảo thiết bị hoặc trình mô phỏng của bạn có bàn phím có thể gửi nội dung đa dạng thức. Bạn có thể sử dụng Bàn phím Google trong Android 7.1 trở lên.

Thêm tính năng hỗ trợ hình ảnh vào IME

Các IME muốn gửi nội dung đa dạng thức tới ứng dụng phải triển khai Commit Content API, như trong ví dụ sau:

  • Ghi đè onStartInput() hoặc onStartInputView() và đọc danh sách các loại nội dung được hỗ trợ của trình chỉnh sửa mục tiêu. Đoạn mã sau đây cho biết cách kiểm tra xem trình chỉnh sửa mục tiêu có chấp nhận hình ảnh GIF hay không.

Kotlin

override fun onStartInputView(editorInfo: EditorInfo, restarting: Boolean) {
    val mimeTypes: Array<String> = EditorInfoCompat.getContentMimeTypes(editorInfo)

    val gifSupported: Boolean = mimeTypes.any {
        ClipDescription.compareMimeTypes(it, "image/gif")
    }

    if (gifSupported) {
        // The target editor supports GIFs. Enable the corresponding content.
    } else {
        // The target editor doesn't support GIFs. Disable the corresponding
        // content.
    }
}

Java

@Override
public void onStartInputView(EditorInfo info, boolean restarting) {
    String[] mimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo);

    boolean gifSupported = false;
    for (String mimeType : mimeTypes) {
        if (ClipDescription.compareMimeTypes(mimeType, "image/gif")) {
            gifSupported = true;
        }
    }

    if (gifSupported) {
        // The target editor supports GIFs. Enable the corresponding content.
    } else {
        // The target editor doesn't support GIFs. Disable the corresponding
        // content.
    }
}

  • Gửi nội dung cho ứng dụng khi người dùng chọn một hình ảnh. Tránh gọi commitContent() khi có bất kỳ văn bản nào đang được soạn, vì điều này có thể khiến trình chỉnh sửa mất tiêu điểm. Đoạn mã sau đây cho biết cách cam kết hình ảnh GIF.

Kotlin

// Commits a GIF image.

// @param contentUri = Content URI of the GIF image to be sent.
// @param imageDescription = Description of the GIF image to be sent.

fun commitGifImage(contentUri: Uri, imageDescription: String) {
    val inputContentInfo = InputContentInfoCompat(
            contentUri,
            ClipDescription(imageDescription, arrayOf("image/gif")),
            null
    )
    val inputConnection = currentInputConnection
    val editorInfo = currentInputEditorInfo
    var flags = 0
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
        flags = flags or InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION
    }
    InputConnectionCompat.commitContent(inputConnection, editorInfo, inputContentInfo, flags, null)
}

Java

// Commits a GIF image.

// @param contentUri = Content URI of the GIF image to be sent.
// @param imageDescription = Description of the GIF image to be sent.

public static void commitGifImage(Uri contentUri, String imageDescription) {
    InputContentInfoCompat inputContentInfo = new InputContentInfoCompat(
            contentUri,
            new ClipDescription(imageDescription, new String[]{"image/gif"}),
            null
    );
    InputConnection inputConnection = getCurrentInputConnection();
    EditorInfo editorInfo = getCurrentInputEditorInfo();
    Int flags = 0;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
        flags |= InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
    }
    InputConnectionCompat.commitContent(
            inputConnection, editorInfo, inputContentInfo, flags, null);
}

Là một tác giả IME, rất có thể bạn phải triển khai trình cung cấp nội dung của riêng mình để phản hồi các yêu cầu URI nội dung. Trường hợp ngoại lệ là nếu IME của bạn hỗ trợ nội dung của các trình cung cấp nội dung hiện có như MediaStore. Để biết thông tin về trình cung cấp nội dung xây dựng, hãy xem tài liệu về trình cung cấp nội dungtrình cung cấp tệp.

Nếu đang xây dựng trình cung cấp nội dung của riêng mình, bạn không nên xuất trình cung cấp đó bằng cách đặt android:exported thành false. Thay vào đó, hãy bật tính năng cấp quyền trong trình cung cấp bằng cách đặt android:grantUriPermission thành true. Sau đó, IME của bạn có thể cấp quyền truy cập URI nội dung khi nội dung được gửi. Có 2 cách để thực hiện việc này:

  • Trên Android 7.1 (API cấp 25) trở lên, khi gọi commitContent(), hãy đặt tham số gắn cờ thành INPUT_CONTENT_GRANT_READ_URI_PERMISSION. Sau đó, đối tượng InputContentInfo mà ứng dụng nhận được có thể yêu cầu và giải phóng quyền đọc tạm thời bằng cách gọi requestPermission()releasePermission().

  • Trên Android 7.0 (API cấp 24) trở xuống, INPUT_CONTENT_GRANT_READ_URI_PERMISSION sẽ bị bỏ qua, vì vậy, hãy cấp quyền cho nội dung theo cách thủ công. Có một cách để thực hiện việc này là sử dụng grantUriPermission(), nhưng bạn có thể triển khai cơ chế riêng đáp ứng các yêu cầu của riêng mình.

Để kiểm tra IME, hãy đảm bảo thiết bị hoặc trình mô phỏng của bạn có một ứng dụng có thể nhận nội dung đa dạng thức. Bạn có thể sử dụng ứng dụng Google Messenger trên Android 7.1 trở lên.