Membuat layanan IsiOtomatis

Layanan IsiOtomatis adalah aplikasi yang memudahkan pengguna untuk mengisi formulir dengan menyuntikkan data ke tampilan aplikasi lain. Layanan IsiOtomatis juga dapat mengambil data pengguna dari tampilan dalam aplikasi dan menyimpannya untuk digunakan di lain waktu. Layanan IsiOtomatis biasanya disediakan oleh aplikasi yang mengelola data pengguna, seperti pengelola sandi.

Android mempermudah pengisian formulir dengan kerangka kerja IsiOtomatis yang tersedia di Android 8.0 (API level 26) dan lebih tinggi. Pengguna dapat memanfaatkan fitur IsiOtomatis hanya jika ada aplikasi yang menyediakan layanan IsiOtomatis pada perangkat mereka.

Halaman ini menunjukkan cara menerapkan layanan IsiOtomatis di aplikasi Anda. Jika Anda mencari sampel kode yang menunjukkan cara menerapkan layanan, lihat Android AutofillFramework Sample. Untuk detail selengkapnya tentang cara kerja layanan IsiOtomatis, lihat dokumentasi referensi dari class AutofillService dan AutofillManager.

Izin dan pernyataan manifes

Aplikasi yang menyediakan layanan IsiOtomatis harus menyertakan deklarasi yang menjelaskan implementasi layanan. Untuk menentukan deklarasi, sertakan elemen <service> dalam manifes aplikasi. Elemen <service> harus menyertakan atribut dan elemen berikut:

Contoh berikut menunjukkan deklarasi layanan IsiOtomatis:

<service
    android:name=".MyAutofillService"
    android:label="My Autofill Service"
    android:permission="android.permission.BIND_AUTOFILL_SERVICE">
    <intent-filter>
        <action android:name="android.service.autofill.AutofillService" />
    </intent-filter>
    <meta-data
        android:name="android.autofill"
        android:resource="@xml/service_configuration" />
</service>

Elemen <meta-data> menyertakan atribut android:resource yang menunjuk ke sumber daya XML dengan detail selengkapnya tentang layanan. Sumber daya service_configuration dalam contoh sebelumnya menetapkan aktivitas yang memungkinkan pengguna untuk mengonfigurasi layanan. Contoh berikut menunjukkan sumber daya XML service_configuration:

<autofill-service
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:settingsActivity="com.example.android.SettingsActivity" />

Untuk informasi selengkpanya tentang sumber daya XML, lihat Menyediakan Sumber Daya.

Perintah untuk mengaktifkan layanan

Aplikasi digunakan sebagai layanan IsiOtomatis setelah menyatakan izin BIND_AUTOFILL_SERVICE dan pengguna mengaktifkannya di setelan perangkat. Aplikasi dapat memverifikasi apakah itu layanan yang saat ini diaktifkan dengan memanggil metode hasEnabledAutofillServices() dari class AutofillManager.

Jika aplikasi ini bukan layanan IsiOtomatis saat ini, maka aplikasi tersebut dapat meminta pengguna untuk mengubah setelan IsiOtomatis dengan menggunakan intent ACTION_REQUEST_SET_AUTOFILL_SERVICE. Intent menampilkan nilai RESULT_OK jika pengguna memilih layanan IsiOtomatis yang cocok dengan paket pemanggil.

Isi tampilan klien

Layanan IsiOtomatis menerima permintaan untuk mengisi tampilan klien saat pengguna berinteraksi dengan aplikasi lain. Jika layanan isiOtomatis memiliki data pengguna yang memenuhi permintaan, maka layanan tersebut akan mengirim data sebagai tanggapannya. Sistem Android menampilkan UI IsiOtomatis dengan data yang tersedia, seperti yang ditampilkan pada gambar 1:

UI IsiOtomatis

Gambar 1. UI IsiOtomatis yang menampilkan dataset.

Kerangka kerja IsiOtomatis menentukan alur kerja untuk mengisi tampilan yang dirancang untuk meminimalkan waktu sistem Android terikat pada layanan IsiOtomatis. Dalam setiap permintaan, sistem Android mengirimkan objek AssistStructure ke layanan dengan memanggil metode onFillRequest(). Layanan IsiOtomatis memeriksa apakah dapat memenuhi permintaan dengan data pengguna yang telah disimpan sebelumnya. Jika dapat memenuhi permintaan, maka layanan mengemas data dalam objek Dataset. Layanan memanggil metode onSuccess() yang meneruskan objek FillResponse, yang berisi objek Dataset. Jika layanan tidak memiliki data untuk memenuhi permintaan, metode tersebut meneruskan null ke metode onSuccess(). Layanan memanggil metode onFailure() jika terjadi kesalahan saat memproses permintaan. Untuk penjelasan mendetail tentang alur kerja, lihat Penggunaan dasar.

Kode berikut menunjukkan contoh metode onFillRequest():

Kotlin

override fun onFillRequest(
    request: FillRequest,
    cancellationSignal: CancellationSignal,
    callback: FillCallback
) {
    // Get the structure from the request
    val context: List<FillContext> = request.fillContexts
    val structure: AssistStructure = context[context.size - 1].structure

    // Traverse the structure looking for nodes to fill out.
    val parsedStructure: ParsedStructure = parseStructure(structure)

    // Fetch user data that matches the fields.
    val (username: String, password: String) = fetchUserData(parsedStructure)

    // Build the presentation of the datasets
    val usernamePresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1)
    usernamePresentation.setTextViewText(android.R.id.text1, "my_username")
    val passwordPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1)
    passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username")

    // Add a dataset to the response
    val fillResponse: FillResponse = FillResponse.Builder()
            .addDataset(Dataset.Builder()
                    .setValue(
                            parsedStructure.usernameId,
                            AutofillValue.forText(username),
                            usernamePresentation
                    )
                    .setValue(
                            parsedStructure.passwordId,
                            AutofillValue.forText(password),
                            passwordPresentation
                    )
                    .build())
            .build()

    // If there are no errors, call onSuccess() and pass the response
    callback.onSuccess(fillResponse)
}

data class ParsedStructure(var usernameId: AutofillId, var passwordId: AutofillId)

data class UserData(var username: String, var password: String)

Java

@Override
public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) {
    // Get the structure from the request
    List<FillContext> context = request.getFillContexts();
    AssistStructure structure = context.get(context.size() - 1).getStructure();

    // Traverse the structure looking for nodes to fill out.
    ParsedStructure parsedStructure = parseStructure(structure);

    // Fetch user data that matches the fields.
    UserData userData = fetchUserData(parsedStructure);

    // Build the presentation of the datasets
    RemoteViews usernamePresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
    usernamePresentation.setTextViewText(android.R.id.text1, "my_username");
    RemoteViews passwordPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
    passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username");

    // Add a dataset to the response
    FillResponse fillResponse = new FillResponse.Builder()
            .addDataset(new Dataset.Builder()
                    .setValue(parsedStructure.usernameId,
                            AutofillValue.forText(userData.username), usernamePresentation)
                    .setValue(parsedStructure.passwordId,
                            AutofillValue.forText(userData.password), passwordPresentation)
                    .build())
            .build();

    // If there are no errors, call onSuccess() and pass the response
    callback.onSuccess(fillResponse);
}

class ParsedStructure {
    AutofillId usernameId;
    AutofillId passwordId;
}

class UserData {
    String username;
    String password;
}

Suatu layanan dapat memiliki lebih dari satu set data yang memenuhi permintaan. Dalam hal ini, sistem Android menampilkan beberapa opsi—satu untuk setiap set data—di UI IsiOtomatis. Contoh kode berikut menunjukkan cara menyediakan beberapa set data sebagai tanggapan:

Kotlin

// Add multiple datasets to the response
val fillResponse: FillResponse = FillResponse.Builder()
        .addDataset(Dataset.Builder()
                .setValue(parsedStructure.usernameId,
                        AutofillValue.forText(user1Data.username), username1Presentation)
                .setValue(parsedStructure.passwordId,
                        AutofillValue.forText(user1Data.password), password1Presentation)
                .build())
        .addDataset(Dataset.Builder()
                .setValue(parsedStructure.usernameId,
                        AutofillValue.forText(user2Data.username), username2Presentation)
                .setValue(parsedStructure.passwordId,
                        AutofillValue.forText(user2Data.password), password2Presentation)
                .build())
        .build()

Java

// Add multiple datasets to the response
FillResponse fillResponse = new FillResponse.Builder()
        .addDataset(new Dataset.Builder()
                .setValue(parsedStructure.usernameId,
                        AutofillValue.forText(user1Data.username), username1Presentation)
                .setValue(parsedStructure.passwordId,
                        AutofillValue.forText(user1Data.password), password1Presentation)
                .build())
        .addDataset(new Dataset.Builder()
                .setValue(parsedStructure.usernameId,
                        AutofillValue.forText(user2Data.username), username2Presentation)
                .setValue(parsedStructure.passwordId,
                        AutofillValue.forText(user2Data.password), password2Presentation)
                .build())
        .build();

Layanan IsiOtomatis dapat membuka objek ViewNode di AssistStructure untuk mengambil data IsiOtomatis yang diperlukan untuk memenuhi permintaan. Layanan dapat mengambil data IsiOtomatis dengan menggunakan metode class ViewNode, misalnya getAutofillId(). Suatu layanan harus dapat menjelaskan isi tampilan untuk memeriksa apakah dapat memenuhi permintaan. Menggunakan atribut autofillHints adalah pendekatan pertama yang harus digunakan layanan untuk menjelaskan isi tampilan. Namun, aplikasi klien harus secara eksplisit menyediakan atribut dalam tampilan mereka sebelum tersedia untuk layanan. Jika aplikasi klien tidak menyediakan atribut autofillHints, layanan harus menggunakan heuristiknya sendiri untuk menjelaskan isinya. Layanan dapat menggunakan metode class lain untuk mendapatkan informasi tentang isi tampilan, seperti getText() atau getHint(). Untuk informasi selengkapnya, lihat Memberikan petunjuk untuk IsiOtomatis. Contoh berikut menunjukkan cara melewati AssistStructure dan mengambil data IsiOtomatis dari objek ViewNode:

Kotlin

fun traverseStructure(structure: AssistStructure) {
    val windowNodes: List<AssistStructure.WindowNode> =
            structure.run {
                (0 until windowNodeCount).map { getWindowNodeAt(it) }
            }

    windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
        val viewNode: ViewNode? = windowNode.rootViewNode
        traverseNode(viewNode)
    }
}

fun traverseNode(viewNode: ViewNode?) {
    if (viewNode?.autofillHints?.isNotEmpty() == true) {
        // If the client app provides autofill hints, you can obtain them using:
        // viewNode.getAutofillHints();
    } else {
        // Or use your own heuristics to describe the contents of a view
        // using methods such as getText() or getHint().
    }

    val children: List<ViewNode>? =
            viewNode?.run {
                (0 until childCount).map { getChildAt(it) }
            }

    children?.forEach { childNode: ViewNode ->
        traverseNode(childNode)
    }
}

Java

public void traverseStructure(AssistStructure structure) {
    int nodes = structure.getWindowNodeCount();

    for (int i = 0; i < nodes; i++) {
        WindowNode windowNode = structure.getWindowNodeAt(i);
        ViewNode viewNode = windowNode.getRootViewNode();
        traverseNode(viewNode);
    }
}

public void traverseNode(ViewNode viewNode) {
    if(viewNode.getAutofillHints() != null && viewNode.getAutofillHints().length > 0) {
        // If the client app provides autofill hints, you can obtain them using:
        // viewNode.getAutofillHints();
    } else {
        // Or use your own heuristics to describe the contents of a view
        // using methods such as getText() or getHint().
    }

    for(int i = 0; i < viewNode.getChildCount(); i++) {
        ViewNode childNode = viewNode.getChildAt(i);
        traverseNode(childNode);
    }
}

Simpan data pengguna

Layanan IsiOtomatis memerlukan data pengguna untuk mengisi tampilan dalam aplikasi. Ketika pengguna secara manual mengisi tampilan, mereka diminta untuk menyimpan data ke layanan IsiOtomatis saat ini, seperti yang ditampilkan pada gambar 2.

IsiOtomatis menyimpan UI

Gambar 2. IsiOtomatis menyimpan UI

Untuk menyimpan data, layanan harus menunjukkan bahwa ia tertarik menyimpan data untuk digunakan di masa mendatang. Sebelum sistem Android mengirim permintaan untuk menyimpan data, ada permintaan pengisian saat layanan memiliki kesempatan untuk mengisi tampilan. Untuk menunjukkan bahwa layanan tersebut berminat untuk menyimpan data, layanan ini menyertakan objek SaveInfo untuk menanggapi permintaan pengisian preseden. Objek SaveInfo berisi setidaknya data berikut:

  • Jenis data pengguna yang akan disimpan. Untuk mengetahui daftar nilai SAVE_DATA yang tersedia, lihat SaveInfo.
  • Rangkaian tampilan minimum yang perlu diubah untuk memicu permintaan simpan. Misalnya, formulir login biasanya mengharuskan pengguna untuk memperbarui tampilan username dan password untuk memicu permintaan simpan.

Objek SaveInfo dikaitkan dengan objek FillResponse, seperti yang ditampilkan pada contoh kode berikut:

Kotlin

override fun onFillRequest(
    request: FillRequest,
    cancellationSignal: CancellationSignal,
    callback: FillCallback
) {
    ...
    // Builder object requires a non-null presentation.
    val notUsed = RemoteViews(packageName, android.R.layout.simple_list_item_1)

    val fillResponse: FillResponse = FillResponse.Builder()
            .addDataset(
                    Dataset.Builder()
                            .setValue(parsedStructure.usernameId, null, notUsed)
                            .setValue(parsedStructure.passwordId, null, notUsed)
                            .build()
            )
            .setSaveInfo(
                    SaveInfo.Builder(
                            SaveInfo.SAVE_DATA_TYPE_USERNAME or SaveInfo.SAVE_DATA_TYPE_PASSWORD,
                            arrayOf(parsedStructure.usernameId, parsedStructure.passwordId)
                    ).build()
            )
            .build()
    ...
}

Java

@Override
public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) {
    ...
    // Builder object requires a non-null presentation.
    RemoteViews notUsed = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);

    FillResponse fillResponse = new FillResponse.Builder()
            .addDataset(new Dataset.Builder()
                    .setValue(parsedStructure.usernameId, null, notUsed)
                    .setValue(parsedStructure.passwordId, null, notUsed)
                    .build())
            .setSaveInfo(new SaveInfo.Builder(
                    SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
                    new AutofillId[] {parsedStructure.usernameId, parsedStructure.passwordId})
                    .build())
            .build();
    ...
}

Layanan IsiOtomatis dapat menerapkan logika untuk mempertahankan data pengguna dalam metode onSaveRequest(), yang biasanya dipanggil setelah aktivitas klien selesai atau ketika aplikasi klien memanggil commit(). Kode berikut menunjukkan contoh metode onSaveRequest():

Kotlin

override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
    // Get the structure from the request
    val context: List<FillContext> = request.fillContexts
    val structure: AssistStructure = context[context.size - 1].structure

    // Traverse the structure looking for data to save
    traverseStructure(structure)

    // Persist the data, if there are no errors, call onSuccess()
    callback.onSuccess()
}

Java

@Override
public void onSaveRequest(SaveRequest request, SaveCallback callback) {
    // Get the structure from the request
    List<FillContext> context = request.getFillContexts();
    AssistStructure structure = context.get(context.size() - 1).getStructure();

    // Traverse the structure looking for data to save
    traverseStructure(structure);

    // Persist the data, if there are no errors, call onSuccess()
    callback.onSuccess();
}

Layanan IsiOtomatis harus mengenkripsi data sensitif sebelum menyimpannya. Akan tetapi, data pengguna menyertakan label atau data yang tidak sensitif. Misalnya, akun pengguna dapat menyertakan label yang menandai data sebagai akun kerja atau pribadi. Layanan tidak boleh mengenkripsi label, yang memungkinkan mereka menggunakan label dalam tampilan presentasi jika pengguna belum mengautentikasi, dan mengganti label dengan data aktual setelah pengguna mengautentikasi.

Tunda UI penyimpanan IsiOtomatis

Mulai dari Android 10, jika Anda menggunakan beberapa layar untuk menerapkan alur kerja IsiOtomatis (misalnya, satu layar untuk kolom nama pengguna dan lainnya untuk sandi), Anda dapat menunda UI penyimpanan IsiOtomatis dengan menggunakan flag SaveInfo.FLAG_DELAY_SAVE.

Jika flag ini disetel, UI penyimpanan IsiOtomatis tidak terpicu saat konteks IsiOtomatis yang terkait dengan tanggapan SaveInfo dilakukan. Sebagai gantinya, Anda dapat menggunakan aktivitas terpisah dalam tugas yang sama untuk mengirimkan permintaan pengisian di masa mendatang kemudian menampilkan UI melalui permintaan simpan. Untuk informasi selengkapnya, lihat SaveInfo.FLAG_DELAY_SAVE.

Memerlukan autentikasi pengguna

Layanan IsiOtomatis dapat menyediakan tingkat keamanan tambahan dengan mengharuskan pengguna mengautentikasi sebelum dapat mengisi tampilan. Skenario berikut adalah kandidat yang baik untuk mengimplementasikan autentikasi pengguna:

  • Data pengguna di aplikasi perlu dibuka menggunakan sandi master atau pemindaian sidik jari.
  • Set data tertentu perlu dibuka kuncinya, misalnya, detail kartu kredit dengan menggunakan kode verifikasi kartu (CVC).

Dalam skenario dengan layanan memerlukan autentikasi pengguna sebelum membuka kunci data, layanan dapat menyajikan data boilerplate atau label dan menentukan Intent yang menangani autentikasi. Jika Anda memerlukan data tambahan untuk memproses permintaan setelah alur autentikasi selesai, Anda dapat menambahkan data tersebut ke intent. Aktivitas autentikasi Anda kemudian dapat menampilkan data ke class AutofillService di aplikasi Anda. Contoh kode berikut menunjukkan cara menentukan apakah permintaan memerlukan otentikasi:

Kotlin

val authPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
    setTextViewText(android.R.id.text1, "requires authentication")
}
val authIntent = Intent(this, AuthActivity::class.java).apply {
    // Send any additional data required to complete the request.
    putExtra(MY_EXTRA_DATASET_NAME, "my_dataset")
}

val intentSender: IntentSender = PendingIntent.getActivity(
        this,
        1001,
        authIntent,
        PendingIntent.FLAG_CANCEL_CURRENT
).intentSender

// Build a FillResponse object that requires authentication.
val fillResponse: FillResponse = FillResponse.Builder()
        .setAuthentication(autofillIds, intentSender, authPresentation)
        .build()

Java

RemoteViews authPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
authPresentation.setTextViewText(android.R.id.text1, "requires authentication");
Intent authIntent = new Intent(this, AuthActivity.class);

// Send any additional data required to complete the request.
authIntent.putExtra(MY_EXTRA_DATASET_NAME, "my_dataset");
IntentSender intentSender = PendingIntent.getActivity(
                this,
                1001,
                authIntent,
                PendingIntent.FLAG_CANCEL_CURRENT
        ).getIntentSender();

// Build a FillResponse object that requires authentication.
FillResponse fillResponse = new FillResponse.Builder()
        .setAuthentication(autofillIds, intentSender, authPresentation)
        .build();

Setelah aktivitas menyelesaikan alur autentikasi, aktivitas tersebut harus memanggil metode setResult() yang meneruskan nilai RESULT_OK dan menyetel ekstra EXTRA_AUTHENTICATION_RESULT ke objek FillResponse yang menyertakan set data yang terisi. Kode berikut menunjukkan contoh cara mengembalikan hasil setelah alur autentikasi selesai:

Kotlin

// The data sent by the service and the structure are included in the intent.
val datasetName: String? = intent.getStringExtra(MY_EXTRA_DATASET_NAME)
val structure: AssistStructure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE)
val parsedStructure: ParsedStructure = parseStructure(structure)
val (username, password) = fetchUserData(parsedStructure)

// Build the presentation of the datasets.
val usernamePresentation =
        RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
            setTextViewText(android.R.id.text1, "my_username")
        }
val passwordPresentation =
        RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
            setTextViewText(android.R.id.text1, "Password for my_username")
        }

// Add the dataset to the response.
val fillResponse: FillResponse = FillResponse.Builder()
        .addDataset(Dataset.Builder()
                .setValue(
                        parsedStructure.usernameId,
                        AutofillValue.forText(username),
                        usernamePresentation
                )
                .setValue(
                        parsedStructure.passwordId,
                        AutofillValue.forText(password),
                        passwordPresentation
                )
                .build()
        ).build()

val replyIntent = Intent().apply {
    // Send the data back to the service.
    putExtra(MY_EXTRA_DATASET_NAME, datasetName)
    putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse)
}

setResult(Activity.RESULT_OK, replyIntent)

Java

Intent intent = getIntent();

// The data sent by the service and the structure are included in the intent.
String datasetName = intent.getStringExtra(MY_EXTRA_DATASET_NAME);
AssistStructure structure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE);
ParsedStructure parsedStructure = parseStructure(structure);
UserData userData = fetchUserData(parsedStructure);

// Build the presentation of the datasets.
RemoteViews usernamePresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
usernamePresentation.setTextViewText(android.R.id.text1, "my_username");
RemoteViews passwordPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username");

// Add the dataset to the response.
FillResponse fillResponse = new FillResponse.Builder()
        .addDataset(new Dataset.Builder()
                .setValue(parsedStructure.usernameId,
                        AutofillValue.forText(userData.username), usernamePresentation)
                .setValue(parsedStructure.passwordId,
                        AutofillValue.forText(userData.password), passwordPresentation)
                .build())
        .build();

Intent replyIntent = new Intent();

// Send the data back to the service.
replyIntent.putExtra(MY_EXTRA_DATASET_NAME, datasetName);
replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse);

setResult(RESULT_OK, replyIntent);

Dalam skenario dengan set data kartu kredit perlu dibuka kuncinya, layanan dapat menampilkan UI yang meminta CVC. Anda dapat menyembunyikan data hingga set data dibuka kuncinya dengan menampilkan data boilerplate, seperti nama bank dan empat digit terakhir nomor kartu kredit. Contoh berikut menunjukkan bagaimana memerlukan autentikasi untuk set data dan menyembunyikan data hingga pengguna memberikan CVC:

Kotlin

// Parse the structure and fetch payment data.
val parsedStructure: ParsedStructure = parseStructure(structure)
val paymentData: Payment = fetchPaymentData(parsedStructure)

// Build the presentation that shows the bank and the last four digits of the credit card number.
// For example 'Bank-1234'.
val maskedPresentation: String = "${paymentData.bank}-" +
        paymentData.creditCardNumber.substring(paymentData.creditCardNumber.length - 4)
val authPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
    setTextViewText(android.R.id.text1, maskedPresentation)
}

// Prepare an intent that displays the UI that asks for the CVC.
val cvcIntent = Intent(this, CvcActivity::class.java)
val cvcIntentSender: IntentSender = PendingIntent.getActivity(
        this,
        1001,
        cvcIntent,
        PendingIntent.FLAG_CANCEL_CURRENT
).intentSender

// Build a FillResponse object that includes a Dataset that requires authentication.
val fillResponse: FillResponse = FillResponse.Builder()
        .addDataset(
                Dataset.Builder()
                        // The values in the dataset are replaced by the actual
                        // data once the user provides the CVC.
                        .setValue(parsedStructure.creditCardId, null, authPresentation)
                        .setValue(parsedStructure.expDateId, null, authPresentation)
                        .setAuthentication(cvcIntentSender)
                        .build()
        ).build()

Java

// Parse the structure and fetch payment data.
ParsedStructure parsedStructure = parseStructure(structure);
Payment paymentData = fetchPaymentData(parsedStructure);

// Build the presentation that shows the bank and the last four digits of the credit card number.
// For example 'Bank-1234'.
String maskedPresentation = paymentData.bank + "-" +
    paymentData.creditCardNumber.subString(paymentData.creditCardNumber.length - 4);
RemoteViews authPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
authPresentation.setTextViewText(android.R.id.text1, maskedPresentation);

// Prepare an intent that displays the UI that asks for the CVC.
Intent cvcIntent = new Intent(this, CvcActivity.class);
IntentSender cvcIntentSender = PendingIntent.getActivity(
        this,
        1001,
        cvcIntent,
        PendingIntent.FLAG_CANCEL_CURRENT
).getIntentSender();

// Build a FillResponse object that includes a Dataset that requires authentication.
FillResponse fillResponse = new FillResponse.Builder()
        .addDataset(new Dataset.Builder()
                // The values in the dataset are replaced by the actual
                // data once the user provides the CVC.
                .setValue(parsedStructure.creditCardId, null, authPresentation)
                .setValue(parsedStructure.expDateId, null, authPresentation)
                .setAuthentication(cvcIntentSender)
                .build())
        .build();

Setelah aktivitas memvalidasi CVC, aktivitas tersebut harus memanggil metode setResult() yang meneruskan nilai RESULT_OK dan menyetel ekstra EXTRA_AUTHENTICATION_RESULT ke objek Dataset yang berisi nomor kartu kredit dan tanggal habis masa berlaku. Set data baru menggantikan set data yang membutuhkan autentikasi dan tampilan segera diisi. Kode berikut menunjukkan contoh cara menampilkan set data setelah pengguna memberikan CVC:

Kotlin

// Parse the structure and fetch payment data.
val parsedStructure: ParsedStructure = parseStructure(structure)
val paymentData: Payment = fetchPaymentData(parsedStructure)

// Build a non-null RemoteViews object that we can use as the presentation when
// creating the Dataset object. This presentation isn't actually used, but the
// Builder object requires a non-null presentation.
val notUsed = RemoteViews(packageName, android.R.layout.simple_list_item_1)

// Create a dataset with the credit card number and expiration date.
val responseDataset: Dataset = Dataset.Builder()
        .setValue(
                parsedStructure.creditCardId,
                AutofillValue.forText(paymentData.creditCardNumber),
                notUsed
        )
        .setValue(
                parsedStructure.expDateId,
                AutofillValue.forText(paymentData.expirationDate),
                notUsed
        )
        .build()

val replyIntent = Intent().apply {
    putExtra(EXTRA_AUTHENTICATION_RESULT, responseDataset)
}

Java

// Parse the structure and fetch payment data.
ParsedStructure parsedStructure = parseStructure(structure);
Payment paymentData = fetchPaymentData(parsedStructure);

// Build a non-null RemoteViews object that we can use as the presentation when
// creating the Dataset object. This presentation isn't actually used, but the
// Builder object requires a non-null presentation.
RemoteViews notUsed = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);

// Create a dataset with the credit card number and expiration date.
Dataset responseDataset = new Dataset.Builder()
        .setValue(parsedStructure.creditCardId,
                AutofillValue.forText(paymentData.creditCardNumber), notUsed)
        .setValue(parsedStructure.expDateId,
                AutofillValue.forText(paymentData.expirationDate), notUsed)
        .build();

Intent replyIntent = new Intent();
replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, responseDataset);

Mengatur data dalam grup logis

Layanan IsiOtomatis harus mengatur data dalam grup logis yang mengisolasi konsep dari domain yang berbeda. Grup logis ini disebut sebagai partisi di halaman ini. Daftar berikut menunjukkan contoh khas partisi dan kolom:

  • Kredensial, yang meliputi kolom nama pengguna dan sandi.
  • Alamat, yang meliputi kolom jalan, kota, negara bagian, dan kode pos.
  • Informasi pembayaran, yang meliputi kolom nomor kartu kredit, tanggal habis masa berlaku, dan kode verifikasi.

Layanan IsiOtomatis yang dengan benar mempartisi data dapat lebih melindungi data penggunanya dengan tidak mengekspos data dari lebih dari satu partisi dalam set data. Misalnya, set data yang menyertakan kredensial tidak harus menyertakan informasi pembayaran. Pengaturan data dalam partisi memungkinkan layanan Anda mengekspos jumlah minimum informasi yang diperlukan untuk memenuhi permintaan.

Pengaturan data dalam partisi memungkinkan layanan mengisi aktivitas yang memiliki tampilan dari beberapa partisi sekaligus mengirim jumlah data minimum ke aplikasi klien. Misalnya, pertimbangkan aktivitas yang menyertakan tampilan untuk nama pengguna, sandi, jalan, dan kota, dan layanan IsiOtomatis yang memiliki data berikut:

Partisi Kolom 1 Kolom 2
Kredensial work_username work_password
personal_username personal_password
Alamat work_street work_city
personal_street personal_city

Layanan dapat menyiapkan set data yang menyertakan partisi kredensial untuk akun pekerjaan dan pribadi. Ketika pengguna memilih satu, respons isi IsiOtomatis berikutnya dapat menyediakan pekerjaan atau alamat pribadi, tergantung pilihan pertama pengguna.

Suatu layanan dapat mengidentifikasi kolom yang berasal dari permintaan dengan memanggil metode isFocused() saat melintasi objek AssistStructure. Ini memungkinkan layanan menyiapkan FillResponse dengan data partisi yang sesuai.

Skenario IsiOtomatis lanjutan

Memberi nomor set data
Respons IsiOtomatis yang besar dapat melebihi ukuran transaksi yang diizinkan dari objek Binder yang mewakili objek yang dapat di-remot yang diperlukan untuk memproses permintaan. Untuk mencegah agar sistem Android tidak mengembalikan pengecualian dalam skenario ini, Anda dapat menjaga FillResponse tetap kecil dengan menambahkan tidak lebih dari 20 Dataset objek sekaligus. Jika respons Anda membutuhkan lebih banyak set data, Anda dapat menambahkan set data yang memungkinkan pengguna tahu bahwa ada lebih banyak informasi dan mengambil kelompok set data berikutnya saat dipilih. Untuk informasi selengkapnya, lihat addDataset(Dataset).
Menyimpan data yang terpecah dalam beberapa layar

Aplikasi sering membagi data pengguna dalam beberapa layar dalam aktivitas yang sama, terutama dalam aktivitas yang digunakan untuk membuat akun pengguna baru. Misalnya, layar pertama meminta nama pengguna, dan jika nama pengguna tersedia, layar akan pindah ke layar kedua, yang meminta sandi. Dalam situasi ini, layanan IsiOtomatis harus menunggu hingga pengguna memasukkan kedua kolom sebelum UI penyimpanan IsiOtomatis dapat ditampilkan. Layanan dapat mengikuti langkah-langkah ini untuk menangani skenario seperti itu:

  1. Dalam permintaan pengisian pertama, layanan menambahkan paket status klien dalam respons, yang berisi ID IsiOtomatis dari kolom parsial yang ada di layar.
  2. Dalam permintaan pengisian kedua, layanan mengambil paket status klien, menyetel ID IsiOtomatis dalam permintaan sebelumnya dari status klien, dan menambahkan ID ini dan flag FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE ke objek SaveInfo yang digunakan dalam respons kedua.
  3. Dalam permintaan simpan, layanan menggunakan objek FillContext yang tepat untuk mendapatkan nilai setiap kolom. Ada satu konteks pengisian setiap permintaan pengisian.

Untuk informasi selengkapnya, lihat Menyimpan saat data dipecah menjadi beberapa layar.

Memberikan logika teardown dan inisialisasi untuk setiap permintaan

Setiap kali ada permintaan pengisian otomatis, sistem Android mengikat ke layanan dan memanggil metode onConnected()-nya. Setelah layanan memproses permintaan, sistem Android memanggil metode onDisconnected() dan melepaskan ikatan dari layanan. Anda dapat menerapkan onConnected() untuk memberikan kode yang berjalan sebelum memproses permintaan dan onDisconnected() untuk memberikan kode yang berjalan setelah memproses permintaan.

Menyesuaikan UI penyimpanan IsiOtomatis

Layanan IsiOtomatis dapat menyesuaikan UI penyimpanan IsiOtomatis untuk membantu pengguna memutuskan apakah mereka ingin mengizinkan layanan untuk menyimpan data mereka. Layanan dapat memberikan informasi tambahan tentang apa yang akan disimpan baik melalui teks sederhana atau melalui tampilan yang disesuaikan. Layanan juga dapat mengubah tampilan tombol yang membatalkan permintaan simpan dan mendapatkan notifikasi saat pengguna mengetuk tombol tersebut. Untuk informasi selengkapnya, lihat dokumentasi referensi SaveInfo.

Mode kompatibilitas

Mode kompatibilitas memungkinkan layanan IsiOtomatis menggunakan struktur aksesibilitas virtual untuk tujuan IsiOtomatis. Ini sangat berguna untuk menyediakan fungsionalitas IsiOtomatis di browser yang belum secara eksplisit mengimplementasikan API IsiOtomatis.

Untuk menguji layanan IsiOtomatis Anda menggunakan mode kompatibilitas, Anda harus secara eksplisit mengizinkan browser atau aplikasi yang memerlukan mode kompatibilitas. Anda dapat memeriksa paket mana yang sudah diizinkan dengan menjalankan perintah berikut:

$ adb shell settings get global autofill_compat_mode_allowed_packages

Jika paket yang diuji tidak terdaftar, Anda dapat mengizinkannya dengan menjalankan perintah ini:

$ adb shell settings put global autofill_compat_mode_allowed_packages pkg1[resId1]:pkg2[resId1,resId2]

...dengan pkgX adalah paket aplikasi. Jika aplikasi adalah browser, maka Anda menggunakan resIdx untuk menentukan ID sumber daya dari kolom input yang berisi URL halaman yang dirender.

Mode kompatibilitas memiliki batasan berikut:

Untuk informasi selengkapnya tentang mode kompatibilitas, termasuk batasan yang terkait dengannya, lihat referensi class AutofillService.