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:
- Atribut
android:nameyang menunjuk ke subclassAutofillServicedi aplikasi yang mengimplementasikan layanan. - Atribut
android:permissionyang mendeklarasikan izinBIND_AUTOFILL_SERVICE. - Elemen
<intent-filter>yang turunan<action>wajibnya menentukan tindakanandroid.service.autofill.AutofillService. - Elemen
<meta-data>opsional yang dapat Anda gunakan untuk memberikan parameter konfigurasi tambahan untuk layanan.
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 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, seperti yang ditunjukkan pada Gambar 1:
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,
layanan 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 mengetahui 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. Saat pengguna mengisi tampilan secara manual, mereka akan diminta untuk menyimpan data ke layanan isi otomatis saat ini, sebagaimana ditunjukkan dalam Gambar 2.
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_DATAyang tersedia, lihatSaveInfo. - Rangkaian tampilan minimum yang harus diubah untuk memicu permintaan simpan.
Misalnya, formulir login biasanya mengharuskan pengguna memperbarui tampilan
usernamedanpassworduntuk 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 menggunakan 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 dengan 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 bahwa 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 umum 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 Anda 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 sambil mengirimkan data relevan dalam jumlah minimum ke aplikasi klien. Misalnya, pertimbangkan aktivitas yang mencakup tampilan untuk nama pengguna, sandi, jalan, dan kota, serta 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 Anda dapat membantu pengguna dalam mengisi kode sekali pakai yang dikirim 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 Anda 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:
- Di layanan isi otomatis, gunakan
hasOngoingSmsRequestdariSmsCodeAutofillClientuntuk 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 ditampilkanfalse. - Di layanan isi otomatis, gunakan
checkPermissionStatedariSmsCodeAutofillClientuntuk memeriksa apakah layanan isi otomatis memiliki izin untuk mengisi otomatis kode sekali pakai. Status izin ini dapat berupaNONE,GRANTED, atauDENIED. Layanan isi otomatis harus menampilkan perintah saran untuk statusNONEdanGRANTED. - Dalam aktivitas autentikasi isi otomatis, gunakan izin
SmsRetriever.SEND_PERMISSIONuntuk mendaftarkan pemrosesanBroadcastReceiveragarSmsCodeRetriever.SMS_CODE_RETRIEVED_ACTIONmenerima hasil kode SMS saat tersedia. Panggil
startSmsCodeRetrieverdiSmsCodeAutofillClientuntuk mulai memproses kode sekali pakai yang dikirim menggunakan 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
Taskyang ditampilkan olehstartSmsCodeRetrievermungkin gagal dengan menampilkanResolvableApiException. Jika hal ini terjadi, Anda harus memanggil metodeResolvableApiException.startResolutionForResult()guna menampilkan dialog izin untuk permintaan izin.Terima hasil kode SMS dari intent, lalu tampilkan kode SMS sebagai respons isi otomatis.
Mengaktifkan isi otomatis di Chrome
Chrome memungkinkan layanan isi otomatis pihak ketiga mengisi formulir secara native sehingga memberikan pengalaman pengguna yang lebih lancar dan sederhana. Untuk menggunakan layanan isi otomatis pihak ketiga guna mengisi otomatis sandi, kunci sandi, dan informasi lainnya seperti alamat dan data pembayaran, pengguna harus memilih Isi otomatis menggunakan layanan lain di setelan Chrome.
Untuk membantu pengguna mendapatkan pengalaman isi otomatis terbaik dengan layanan Anda dan Chrome di Android, penyedia layanan isi otomatis harus mendorong pengguna mereka untuk menentukan penyedia layanan pilihan mereka di setelan Chrome.
Untuk membantu pengguna mengaktifkan tombol, developer dapat:
- Mengueri setelan Chrome dan mempelajari apakah pengguna ingin menggunakan layanan isi otomatis pihak ketiga.
- Link dalam ke halaman setelan Chrome tempat pengguna dapat mengaktifkan layanan isi otomatis pihak ketiga.
Menentukan versi Chrome maksimum untuk mode kompatibilitas
Chrome berhenti mendukung mode kompatibilitas mulai versi 137 dan beralih ke Isi Otomatis Android. Mempertahankan mode kompatibilitas dapat menyebabkan masalah stabilitas. Tentukan versi maksimum paket Chrome yang mendukung mode kompatibilitas untuk stabilitas sebagai berikut.
<autofill-service>
...
<compatibility-package android:name="com.android.chrome" android:maxLongVersionCode="711900039" />
<compatibility-package android:name="com.chrome.beta" android:maxLongVersionCode="711900039" />
<compatibility-package android:name="com.chrome.dev" android:maxLongVersionCode="711900039" />
<compatibility-package android:name="com.chrome.canary" android:maxLongVersionCode="711900039" />
...
</autofill-service>
Membaca setelan Chrome
Aplikasi apa pun dapat membaca apakah Chrome menggunakan mode isi otomatis pihak ketiga yang memungkinkannya menggunakan Isi Otomatis Android. Chrome menggunakan ContentProvider Android untuk mengomunikasikan informasi tersebut. Nyatakan di manifes Android Anda saluran yang ingin Anda baca setelannya:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY"/>
<queries>
<!-- To Query Chrome Beta: -->
<package android:name="com.chrome.beta" />
<!-- To Query Chrome Stable: -->
<package android:name="com.android.chrome" />
</queries>
Kemudian, gunakan ContentResolver Android untuk meminta informasi tersebut dengan
membuat URI konten:
Kotlin
val CHROME_CHANNEL_PACKAGE = "com.android.chrome" // Chrome Stable.
val CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider"
val THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state"
val THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode"
val uri = Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(CHROME_CHANNEL_PACKAGE + CONTENT_PROVIDER_NAME)
.path(THIRD_PARTY_MODE_ACTIONS_URI_PATH)
.build()
val cursor = contentResolver.query(
uri,
arrayOf(THIRD_PARTY_MODE_COLUMN), // projection
null, // selection
null, // selectionArgs
null // sortOrder
)
if (cursor == null) {
// Terminate now! Chromium versions older than this don't provide this information.
}
cursor?.use { // Use the safe call operator and the use function for auto-closing
if (it.moveToFirst()) { // Check if the cursor has any rows
val index = it.getColumnIndex(THIRD_PARTY_MODE_COLUMN)
if (index != -1) { // Check if the column exists
val value = it.getInt(index)
if (0 == value) {
// 0 means that the third party mode is turned off. Chrome uses its built-in
// password manager. This is the default for new users.
} else {
// 1 means that the third party mode is turned on. Chrome forwards all
// autofill requests to Android Autofill. Users have to opt-in for this.
}
} else {
// Handle the case where the column doesn't exist. Log a warning, perhaps.
Log.w("Autofill", "Column $THIRD_PARTY_MODE_COLUMN not found in cursor")
}
}
} // The cursor is automatically closed here
Java
final String CHROME_CHANNEL_PACKAGE = "com.android.chrome"; // Chrome Stable.
final String CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider";
final String THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state";
final String THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode";
final Uri uri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(CHROME_CHANNEL_PACKAGE + CONTENT_PROVIDER_NAME)
.path(THIRD_PARTY_MODE_ACTIONS_URI_PATH)
.build();
final Cursor cursor = getContentResolver().query(
uri,
/*projection=*/new String[] {THIRD_PARTY_MODE_COLUMN},
/*selection=*/ null,
/*selectionArgs=*/ null,
/*sortOrder=*/ null);
if (cursor == null) {
// Terminate now! Chromium versions older than this don't provide this information.
}
cursor.moveToFirst(); // Retrieve the result;
int index = cursor.getColumnIndex(THIRD_PARTY_MODE_COLUMN);
if (0 == cursor.getInt(index)) {
// 0 means that the third party mode is turned off. Chrome uses its built-in
// password manager. This is the default for new users.
} else {
// 1 means that the third party mode is turned on. Chrome forwards all
// autofill requests to Android Autofill. Users have to opt-in for this.
}
Deep link ke setelan Chrome
Untuk membuat deep link ke halaman setelan Chrome tempat pengguna dapat mengaktifkan layanan isi otomatis pihak ketiga, gunakan Intent Android. Pastikan untuk mengonfigurasi
tindakan dan kategori seperti yang ditunjukkan dalam contoh ini:
Kotlin
val autofillSettingsIntent = Intent(Intent.ACTION_APPLICATION_PREFERENCES)
autofillSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT)
autofillSettingsIntent.addCategory(Intent.CATEGORY_APP_BROWSER)
autofillSettingsIntent.addCategory(Intent.CATEGORY_PREFERENCE)
// Invoking the intent with a chooser allows users to select the channel they
// want to configure. If only one browser reacts to the intent, the chooser is
// skipped.
val chooser = Intent.createChooser(autofillSettingsIntent, "Pick Chrome Channel")
startActivity(chooser)
// If the caller knows which Chrome channel they want to configure,
// they can instead add a package hint to the intent, e.g.
val specificChromeIntent = Intent(Intent.ACTION_APPLICATION_PREFERENCES) // Create a *new* intent
specificChromeIntent.addCategory(Intent.CATEGORY_DEFAULT)
specificChromeIntent.addCategory(Intent.CATEGORY_APP_BROWSER)
specificChromeIntent.addCategory(Intent.CATEGORY_PREFERENCE)
specificChromeIntent.setPackage("com.android.chrome") // Set the package on the *new* intent
startActivity(specificChromeIntent) // Start the *new* intent
Java
Intent autofillSettingsIntent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES);
autofillSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
autofillSettingsIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
autofillSettingsIntent.addCategory(Intent.CATEGORY_PREFERENCE);
// Invoking the intent with a chooser allows users to select the channel they
// want to configure. If only one browser reacts to the intent, the chooser is
// skipped.
Intent chooser = Intent.createChooser(autofillSettingsIntent, "Pick Chrome Channel");
startActivity(chooser);
// If the caller knows which Chrome channel they want to configure,
// they can instead add a package hint to the intent, e.g.
autofillSettingsIntent.setPackage("com.android.chrome");
startActivity(autofillSettingsIntent);
Skenario isi otomatis lanjutan
Gunakan isi otomatis dalam skenario berikut:
Mengintegrasikan 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 yang besar dapat melebihi ukuran transaksi yang diizinkan dari objek
Binder yang merepresentasikan objek yang dapat diakses dari 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 Anda memerlukan 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 mengetahui informasi selengkapnya, lihat
addDataset(Dataset).
Menyimpan pemisahan data di beberapa layar
Aplikasi sering kali memisahkan data pengguna di beberapa layar dalam aktivitas yang sama, seperti untuk membuat akun pengguna baru. Misalnya, layar pertama mungkin meminta nama pengguna, dan layar kedua meminta sandi. Dalam situasi ini, layanan isi otomatis Anda harus menunggu hingga pengguna memasukkan data ke semua kolom yang relevan sebelum menampilkan UI simpan isi otomatis. Ikuti langkah-langkah berikut untuk menangani skenario tersebut:
- Dalam permintaan pengisian pertama, tambahkan paket status klien dalam respons yang berisi ID isi otomatis kolom parsial yang ada di layar.
- Dalam permintaan pengisian kedua, ambil paket status klien, dapatkan
ID isi otomatis yang ditetapkan dalam permintaan sebelumnya dari status klien, lalu tambahkan
ID ini dan tanda
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLEke objekSaveInfoyang digunakan dalam respons kedua. Dalam permintaan simpan, gunakan objek
FillContextyang tepat untuk mendapatkan nilai 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 mereka. Layanan dapat memberikan
informasi tambahan tentang data yang akan disimpan baik melalui teks maupun 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 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 ada dalam 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_INVISIBLEatau metodesetTrigger()dipanggil.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLEdisetel 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.