Membuat layanan isi otomatis

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

Android memudahkan pengisian formulir dengan framework isi otomatis yang tersedia di Android 8.0 (API level 26) dan yang lebih baru. Pengguna dapat memanfaatkan fitur isi otomatis hanya jika ada aplikasi yang menyediakan layanan isi otomatis di perangkat mereka.

Halaman ini menunjukkan cara mengimplementasikan layanan isi otomatis di aplikasi. Jika Anda mencari contoh kode yang menunjukkan cara mengimplementasikan layanan, lihat contoh AutofillFramework di Java atau Kotlin. Untuk detail selengkapnya tentang cara kerja layanan isi otomatis, lihat halaman referensi untuk class AutofillService dan AutofillManager.

Izin dan deklarasi manifes

Aplikasi yang menyediakan layanan isi otomatis 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 isi otomatis:

<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 resource XML dengan detail selengkapnya tentang layanan. Resource service_configuration dalam contoh sebelumnya menetapkan aktivitas yang memungkinkan pengguna mengonfigurasi layanan. Contoh berikut menunjukkan resource XML service_configuration:

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

Untuk informasi selengkapnya tentang resource XML, lihat Ringkasan resource aplikasi.

Perintah untuk mengaktifkan layanan

Aplikasi digunakan sebagai layanan isi otomatis setelah mendeklarasikan izin BIND_AUTOFILL_SERVICE dan pengguna mengaktifkannya di setelan perangkat. Aplikasi dapat memverifikasi apakah layanan saat ini diaktifkan dengan memanggil metode hasEnabledAutofillServices() class AutofillManager.

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

Mengisi tampilan klien

Layanan isi otomatis menerima permintaan untuk mengisi tampilan klien saat pengguna berinteraksi dengan aplikasi lain. Jika memiliki data pengguna yang memenuhi permintaan, layanan isi otomatis akan mengirimkan data tersebut dalam respons. Sistem Android menampilkan UI isi otomatis dengan data yang tersedia, sebagaimana ditampilkan dalam gambar 1:

UI isi otomatis

Gambar 1. UI isi otomatis yang menampilkan set data.

Framework isi otomatis menentukan alur kerja untuk mengisi tampilan yang dirancang untuk meminimalkan waktu sistem Android terikat pada layanan isi otomatis. Dalam setiap permintaan, sistem Android mengirimkan objek AssistStructure ke layanan dengan memanggil metode onFillRequest().

Layanan isi otomatis memeriksa apakah dapat memenuhi permintaan dengan data pengguna yang telah disimpan sebelumnya. Jika dapat memenuhi permintaan, layanan akan memaketkan data tersebut 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 akan meneruskan null ke metode onSuccess().

Layanan memanggil metode onFailure() jika terjadi error saat memproses permintaan. Untuk penjelasan mendetail tentang alur kerja, lihat deskripsi di halaman referensi AutofillService.

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

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 isi otomatis. Contoh kode berikut menunjukkan cara menyediakan beberapa set data dalam respons:

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 isi otomatis dapat membuka objek ViewNode di AssistStructure untuk mengambil data isi otomatis yang diperlukan untuk memenuhi permintaan. Layanan dapat mengambil data isi otomatis menggunakan metode class ViewNode, seperti getAutofillId().

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 agar tersedia untuk layanan.

Jika aplikasi klien tidak menyediakan atribut autofillHints, layanan harus menggunakan heuristiknya sendiri untuk menjelaskan isinya. Layanan dapat menggunakan metode dari class lain, seperti getText() atau getHint(), untuk mendapatkan informasi tentang isi tampilan. Untuk informasi selengkapnya, lihat Memberikan petunjuk untuk isi otomatis.

Contoh berikut menunjukkan cara melewati AssistStructure dan mengambil data isi otomatis 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);
    }
}

Menyimpan data pengguna

Layanan isi otomatis memerlukan data pengguna untuk mengisi tampilan dalam aplikasi. Bila secara manual mengisi tampilan, pengguna akan diminta untuk menyimpan data ke layanan isi otomatis saat ini, sebagaimana ditunjukkan dalam gambar 2.

UI simpan isi otomatis

Gambar 2. UI simpan isi otomatis.

Untuk menyimpan data, layanan harus menunjukkan ketertarikan terhadap penyimpanan data untuk digunakan di masa mendatang. Sebelum sistem Android mengirim permintaan untuk menyimpan data, ada permintaan pengisian yang memberi layanan kesempatan untuk mengisi tampilan. Untuk menunjukkan minat terhadap penyimpanan data, layanan menyertakan objek SaveInfo dalam respons terhadap permintaan pengisian. Objek SaveInfo berisi setidaknya data berikut:

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

Objek SaveInfo terkait dengan objek FillResponse, sebagaimana ditunjukkan dalam 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 isi otomatis dapat mengimplementasikan logika untuk mempertahankan data pengguna dalam metode onSaveRequest(), yang biasanya dipanggil setelah aktivitas klien selesai atau saat 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 isi otomatis harus mengenkripsi data sensitif sebelum mempertahankannya. Namun, data pengguna dapat 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. Dengan tidak mengenkripsi label, layanan dapat menggunakan label dalam tampilan presentasi jika pengguna belum melakukan autentikasi. Kemudian, layanan dapat mengganti label dengan data sebenarnya setelah pengguna melakukan autentikasi.

Menunda UI simpan isi otomatis

Mulai Android 10, jika Anda menggunakan beberapa layar untuk mengimplementasikan alur kerja isi otomatis—misalnya, satu layar untuk kolom nama pengguna dan lainnya untuk sandi—Anda dapat menunda UI simpan isi otomatis menggunakan tanda SaveInfo.FLAG_DELAY_SAVE.

Jika tanda ini disetel, UI simpan isi otomatis tidak terpicu saat konteks isi otomatis terkait respons SaveInfo di-commit. Sebagai gantinya, Anda dapat menggunakan aktivitas terpisah dalam tugas yang sama untuk mengirimkan permintaan pengisian di masa mendatang, lalu menampilkan UI melalui permintaan simpan. Untuk informasi selengkapnya, lihat SaveInfo.FLAG_DELAY_SAVE.

Mengharuskan autentikasi pengguna

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

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

Dalam skenario ketika layanan memerlukan autentikasi pengguna sebelum membuka data, layanan dapat menampilkan data boilerplate atau label dan menentukan Intent yang menangani autentikasi. Jika 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 autentikasi:

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 menyelesaikan alur autentikasi, aktivitas harus memanggil metode setResult(), yang meneruskan nilai RESULT_OK dan menyetel tambahan EXTRA_AUTHENTICATION_RESULT ke objek FillResponse yang menyertakan set data terisi. Kode berikut menunjukkan contoh cara menampilkan 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 ketika set data kartu kredit perlu dibuka, layanan dapat menampilkan UI yang meminta CVC. Anda dapat menyembunyikan data hingga set data dibuka dengan menampilkan data boilerplate, seperti nama bank dan empat digit terakhir nomor kartu kredit. Contoh berikut menunjukkan cara mengharuskan 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, such as '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, such as '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 memvalidasi CVC, aktivitas harus memanggil metode setResult(), yang meneruskan nilai RESULT_OK dan menyetel tambahan EXTRA_AUTHENTICATION_RESULT ke objek Dataset yang berisi nomor kartu kredit dan tanggal habis masa berlaku. Set data baru menggantikan set data yang memerlukan 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 to 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 to 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 isi otomatis harus mengatur data dalam grup logis yang mengisolasi konsep dari domain berbeda. Di halaman ini, grup logis ini disebut sebagai partisi. Daftar berikut menunjukkan contoh khas partisi dan kolom:

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

Layanan isi otomatis yang dengan benar melakukan partisi data dapat lebih baik melindungi data penggunanya dengan tidak mengekspos data dari lebih dari satu partisi dalam set data. Misalnya, set data yang menyertakan kredensial tidak perlu menyertakan informasi pembayaran. Pengaturan data dalam partisi memungkinkan layanan mengekspos informasi relevan yang diperlukan untuk memenuhi permintaan dalam jumlah minimum.

Pengaturan data dalam partisi memungkinkan layanan mengisi aktivitas yang memiliki tampilan dari beberapa partisi sekaligus mengirim data yang relevan dalam jumlah minimum ke aplikasi klien. Misalnya, pertimbangkan aktivitas yang menyertakan tampilan untuk nama pengguna, sandi, jalan, dan kota, dan layanan isi otomatis 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 kerja dan pribadi. Saat pengguna memilih set data, respons isi otomatis berikutnya dapat memberikan alamat kantor atau pribadi, bergantung pada pilihan pertama pengguna.

Layanan dapat mengidentifikasi kolom yang menghasilkan permintaan dengan memanggil metode isFocused() saat melewati objek AssistStructure. Hal ini memungkinkan layanan menyiapkan FillResponse dengan data partisi yang sesuai.

Isi otomatis kode sekali pakai SMS

Layanan isi otomatis dapat membantu pengguna dalam mengisi kode sekali pakai yang dikirim melalui SMS menggunakan SMS Retriever API.

Untuk menggunakan fitur ini, persyaratan berikut harus dipenuhi:

  • Layanan isi otomatis berjalan di Android 9 (API level 28) atau yang lebih baru.
  • Pengguna memberikan izin agar layanan isi otomatis membaca kode sekali pakai dari SMS.
  • Aplikasi yang disediakan untuk isi otomatis belum menggunakan SMS Retriever API untuk membaca kode sekali pakai.

Layanan isi otomatis dapat menggunakan SmsCodeAutofillClient, yang tersedia dengan memanggil SmsCodeRetriever.getAutofillClient() dari Layanan Google Play 19.0.56 atau yang lebih baru.

Langkah utama untuk menggunakan API ini dalam layanan isi otomatis adalah:

  1. Di layanan isi otomatis, gunakan hasOngoingSmsRequest dari SmsCodeAutofillClient untuk menentukan apakah sudah ada permintaan aktif untuk nama paket aplikasi yang Anda isi otomatis. Layanan isi otomatis hanya akan menampilkan perintah saran jika nilai yang ditampilkan false.
  2. Di layanan isi otomatis, gunakan checkPermissionState dari SmsCodeAutofillClient untuk memeriksa apakah layanan isi otomatis memiliki izin untuk mengisi otomatis kode sekali pakai. Status izin ini dapat berupa NONE, GRANTED, atau DENIED. Layanan isi otomatis akan menampilkan perintah saran untuk status NONE dan GRANTED.
  3. Dalam aktivitas autentikasi isi otomatis, gunakan izin SmsRetriever.SEND_PERMISSION untuk mendaftarkan pemrosesan BroadcastReceiver agar SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION menerima hasil kode SMS saat tersedia.
  4. Panggil startSmsCodeRetriever di SmsCodeAutofillClient untuk mulai memproses kode sekali pakai yang dikirim melalui SMS. Jika pengguna memberikan izin pada layanan isi otomatis untuk mengambil kode sekali pakai dari SMS, layanan ini akan mencari pesan SMS yang diterima dalam satu hingga lima menit terakhir dari sekarang.

    Jika layanan isi otomatis perlu meminta izin pengguna untuk membaca kode sekali pakai, maka Task yang ditampilkan oleh startSmsCodeRetriever mungkin gagal dengan menampilkan ResolvableApiException. Jika hal ini terjadi, Anda harus memanggil metode ResolvableApiException.startResolutionForResult() guna menampilkan dialog izin untuk permintaan izin.

  5. Terima hasil kode SMS dari intent, lalu tampilkan kode SMS sebagai respons isi otomatis.

Skenario isi otomatis lanjutan

Mengintegrasi dengan keyboard
Mulai dari Android 11, platform ini memungkinkan keyboard dan editor metode input (IME) lainnya menampilkan saran isi otomatis inline, bukan menggunakan menu pull-down. Untuk informasi selengkapnya tentang cara layanan isi otomatis mendukung fungsi ini, lihat Mengintegrasikan isi otomatis dengan keyboard.
Memberi nomor set data
Respons isi otomatis besar dapat melebihi batas ukuran transaksi objek Binder yang merepresentasikan objek jarak jauh yang diperlukan untuk memproses permintaan. Agar sistem Android tidak menampilkan pengecualian dalam skenario ini, Anda dapat membuat FillResponse tetap kecil dengan menambahkan tidak lebih dari 20 objek Dataset sekaligus. Jika respons membutuhkan lebih banyak set data, Anda dapat menambahkan set data yang memungkinkan pengguna mengetahui bahwa ada lebih banyak informasi dan mengambil grup set data berikutnya saat dipilih. Untuk informasi selengkapnya, lihat addDataset(Dataset).
Menyimpan pemisahan data di beberapa layar

Aplikasi sering kali memisahkan data pengguna menjadi 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 kedua akan meminta sandi. Dalam situasi ini, layanan isi otomatis harus menunggu hingga pengguna memasukkan kedua kolom agar UI simpan isi otomatis dapat ditampilkan. Ikuti langkah-langkah berikut untuk menangani skenario tersebut:

  1. Dalam permintaan pengisian pertama, tambahkan paket status klien dalam respons yang berisi ID isi otomatis kolom parsial yang ada di layar.
  2. Pada permintaan pengisian kedua, ambil paket status klien, setel ID isi otomatis dalam permintaan sebelumnya dari status klien, lalu tambahkan ID ini dan tanda FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE ke SaveInfo objek yang digunakan dalam respons kedua.
  3. Dalam permintaan simpan, gunakan objek FillContext yang tepat untuk mendapatkan nilai dari setiap kolom. Ada satu konteks pengisian per permintaan pengisian.

Untuk informasi selengkapnya, lihat Menyimpan saat data dipisah 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(). Setelah layanan memproses permintaan, sistem Android memanggil metode onDisconnected() dan melepaskan ikatan dari layanan. Anda dapat mengimplementasikan onConnected() untuk memberikan kode yang berjalan sebelum memproses permintaan dan onDisconnected() untuk memberikan kode yang berjalan setelah memproses permintaan.

Menyesuaikan UI simpan isi otomatis

Layanan isi otomatis dapat menyesuaikan UI simpan isi otomatis untuk membantu pengguna memutuskan apakah mereka ingin mengizinkan layanan untuk menyimpan data. Layanan dapat memberikan informasi tambahan tentang data yang akan disimpan baik melalui teks sederhana maupun 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 halaman referensi SaveInfo.

Mode kompatibilitas

Mode kompatibilitas memungkinkan layanan isi otomatis menggunakan struktur virtual aksesibilitas untuk tujuan isi otomatis. Hal ini sangat berguna untuk menyediakan fungsi isi otomatis di browser yang tidak secara eksplisit mengimplementasikan API isi otomatis.

Untuk menguji layanan isi otomatis menggunakan mode kompatibilitas, tambahkan secara eksplisit browser atau aplikasi yang memerlukan mode kompatibilitas ke daftar yang diizinkan. Anda dapat memeriksa paket mana yang sudah ditambahkan ke daftar yang diizinkan dengan menjalankan perintah berikut:

$ adb shell settings get global autofill_compat_mode_allowed_packages

Jika paket yang diuji tidak tercantum, tambahkan dengan menjalankan perintah berikut, dengan pkgX adalah paket aplikasi:

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

Jika aplikasi adalah browser, gunakan resIdx untuk menentukan ID resource dari kolom input yang berisi URL halaman yang dirender.

Mode kompatibilitas memiliki batasan berikut:

  • Permintaan simpan dipicu saat layanan menggunakan tanda FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE atau metode setTrigger() dipanggil. FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE disetel secara default saat menggunakan mode kompatibilitas.
  • Nilai teks node mungkin tidak tersedia dalam metode onSaveRequest(SaveRequest, SaveCallback).

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