บริการป้อนข้อความอัตโนมัติเป็นแอปที่ช่วยให้ผู้ใช้กรอกแบบฟอร์มได้ง่ายขึ้นด้วยการแทรกข้อมูลลงในมุมมองของแอปอื่นๆ บริการป้อนข้อความอัตโนมัติยังดึงข้อมูลผู้ใช้จากมุมมองในแอปและจัดเก็บไว้เพื่อใช้ในภายหลังได้ด้วย โดยทั่วไปแล้ว บริการป้อนข้อความอัตโนมัติจะให้บริการโดยแอปที่จัดการข้อมูลผู้ใช้ เช่น เครื่องมือจัดการรหัสผ่าน
Android ช่วยให้คุณกรอกแบบฟอร์มได้ง่ายขึ้นด้วยเฟรมเวิร์กการป้อนข้อความอัตโนมัติที่มีให้ใช้งานใน Android 8.0 (API ระดับ 26) ขึ้นไป ผู้ใช้จะใช้ประโยชน์จากฟีเจอร์ป้อนข้อความอัตโนมัติได้ก็ต่อเมื่อมีแอปที่ให้บริการป้อนข้อความอัตโนมัติในอุปกรณ์เท่านั้น
หน้านี้แสดงวิธีใช้บริการป้อนข้อความอัตโนมัติในแอป หากคุณกำลังมองหาตัวอย่างโค้ดที่แสดงวิธีใช้บริการ โปรดดูตัวอย่าง AutofillFramework ใน Java หรือ Kotlin
ดูรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการทำงานของบริการป้อนข้อความอัตโนมัติได้ที่หน้าข้อมูลอ้างอิงสำหรับคลาส AutofillService
และ AutofillManager
การประกาศและสิทธิ์ของไฟล์ Manifest
แอปที่ให้บริการป้อนข้อความอัตโนมัติต้องมีประกาศที่อธิบายการใช้งานบริการ หากต้องการระบุประกาศ ให้ใส่องค์ประกอบ <service>
ในไฟล์ Manifest ของแอป องค์ประกอบ <service>
ต้องมีแอตทริบิวต์และองค์ประกอบต่อไปนี้
- แอตทริบิวต์
android:name
ที่ชี้ไปยังคลาสย่อยของAutofillService
ในแอปที่ใช้บริการ android:permission
แอตทริบิวต์ที่ประกาศสิทธิ์BIND_AUTOFILL_SERVICE
- องค์ประกอบ
<intent-filter>
ที่มีองค์ประกอบย่อย<action>
ที่ต้องระบุการดำเนินการandroid.service.autofill.AutofillService
- องค์ประกอบ
<meta-data>
ไม่บังคับที่คุณสามารถใช้เพื่อระบุพารามิเตอร์การกําหนดค่าเพิ่มเติมสําหรับบริการ
ตัวอย่างต่อไปนี้แสดงการประกาศบริการป้อนข้อความอัตโนมัติ
<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>
องค์ประกอบ <meta-data>
มีแอตทริบิวต์ android:resource
ที่ชี้ไปยังทรัพยากร XML พร้อมรายละเอียดเพิ่มเติมเกี่ยวกับบริการ
ทรัพยากร service_configuration
ในตัวอย่างก่อนหน้านี้ระบุกิจกรรมที่อนุญาตให้ผู้ใช้กําหนดค่าบริการ ตัวอย่างต่อไปนี้แสดงทรัพยากร XML service_configuration
<autofill-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.example.android.SettingsActivity" />
ดูข้อมูลเพิ่มเติมเกี่ยวกับทรัพยากร XML ได้ที่ภาพรวมของทรัพยากรแอป
ข้อความแจ้งให้เปิดใช้บริการ
ระบบจะใช้แอปเป็นบริการป้อนข้อความอัตโนมัติหลังจากที่แอปประกาศBIND_AUTOFILL_SERVICE
สิทธิ์และผู้ใช้เปิดใช้ในการตั้งค่าอุปกรณ์ แอปจะยืนยันได้ว่าเป็นบริการที่เปิดใช้อยู่ในปัจจุบันหรือไม่โดยเรียกใช้เมธอด hasEnabledAutofillServices()
ของคลาส AutofillManager
หากแอปไม่ใช่บริการป้อนข้อความอัตโนมัติปัจจุบัน แอปจะขอให้ผู้ใช้เปลี่ยนการตั้งค่าการป้อนข้อความอัตโนมัติได้โดยใช้ Intent ACTION_REQUEST_SET_AUTOFILL_SERVICE
Intent จะแสดงผลค่า RESULT_OK
หากผู้ใช้เลือกบริการป้อนข้อความอัตโนมัติที่ตรงกับแพ็กเกจของผู้เรียก
กรอกข้อมูลมุมมองของลูกค้า
บริการป้อนข้อความอัตโนมัติจะได้รับการร้องขอให้กรอกข้อมูลในมุมมองไคลเอ็นต์เมื่อผู้ใช้โต้ตอบกับแอปอื่นๆ หากบริการป้อนข้อความอัตโนมัติมีข้อมูลผู้ใช้ที่ตรงกับคำขอ บริการจะส่งข้อมูลในการตอบกลับ ระบบ Android จะแสดง UI การป้อนข้อความอัตโนมัติพร้อมข้อมูลที่มีอยู่ดังที่แสดงในรูปที่ 1
เฟรมเวิร์กการป้อนข้อความอัตโนมัติจะกำหนดเวิร์กโฟลว์ในการกรอกข้อมูลในมุมมองที่ออกแบบมาเพื่อลดเวลาที่ระบบ Android ต้องเชื่อมโยงกับบริการป้อนข้อความอัตโนมัติ ในคำขอแต่ละรายการ ระบบ Android จะส่งออบเจ็กต์ AssistStructure
ไปยังบริการโดยเรียกใช้เมธอด onFillRequest()
บริการป้อนข้อความอัตโนมัติจะตรวจสอบว่าสามารถตอบสนองคำขอด้วยข้อมูลผู้ใช้ที่เก็บไว้ก่อนหน้านี้ได้หรือไม่ หากสามารถตอบสนองคําขอได้ บริการจะแพ็กเกจข้อมูลในออบเจ็กต์ Dataset
บริการเรียกใช้เมธอด onSuccess()
โดยส่งออบเจ็กต์ FillResponse
ที่มีออบเจ็กต์ Dataset
หากบริการไม่มีข้อมูลที่จะตอบสนองคําขอ ระบบจะส่ง null
ไปยังเมธอด onSuccess()
บริการจะเรียกใช้เมธอด onFailure()
แทนหากเกิดข้อผิดพลาดขณะประมวลผลคำขอ ดูคำอธิบายเวิร์กโฟลว์โดยละเอียดได้ที่คำอธิบายในAutofillService
หน้าข้อมูลอ้างอิง
โค้ดต่อไปนี้แสดงตัวอย่างเมธอด 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; }
บริการหนึ่งอาจมีชุดข้อมูลมากกว่า 1 ชุดที่ตรงกับคำขอ ในกรณีนี้ ระบบ Android จะแสดงตัวเลือกหลายรายการ (1 รายการสำหรับชุดข้อมูลแต่ละชุด) ใน UI การป้อนข้อความอัตโนมัติ ตัวอย่างโค้ดต่อไปนี้แสดงวิธีระบุชุดข้อมูลหลายชุดในการตอบกลับ
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();
บริการป้อนข้อความอัตโนมัติสามารถไปยังออบเจ็กต์ ViewNode
ใน AssistStructure
เพื่อดึงข้อมูลป้อนข้อความอัตโนมัติที่จําเป็นในการดำเนินการตามคําขอ บริการจะเรียกข้อมูลป้อนข้อความอัตโนมัติได้โดยใช้เมธอดของคลาส ViewNode
เช่น getAutofillId()
บริการต้องอธิบายเนื้อหาของมุมมองเพื่อตรวจสอบว่าสามารถตอบสนองคำขอได้หรือไม่ การใช้แอตทริบิวต์ autofillHints
เป็นแนวทางแรกๆ ที่บริการต้องใช้เพื่ออธิบายเนื้อหาของมุมมอง อย่างไรก็ตาม แอปไคลเอ็นต์ต้องระบุแอตทริบิวต์ในมุมมองอย่างชัดเจนก่อนจึงจะพร้อมให้บริการ
หากแอปไคลเอ็นต์ไม่ได้ระบุแอตทริบิวต์ autofillHints
บริการต้องใช้วิธีการหาค่าประมาณของตนเองเพื่ออธิบายเนื้อหา
บริการสามารถใช้เมธอดจากคลาสอื่นๆ เช่น getText()
หรือ getHint()
เพื่อรับข้อมูลเกี่ยวกับเนื้อหาของมุมมอง
ดูข้อมูลเพิ่มเติมได้ที่ระบุคำแนะนำสำหรับการป้อนข้อความอัตโนมัติ
ตัวอย่างต่อไปนี้แสดงวิธีไปยังส่วนต่างๆ ของ AssistStructure
และดึงข้อมูลป้อนข้อความอัตโนมัติจากออบเจ็กต์ 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); } }
บันทึกข้อมูลผู้ใช้
บริการป้อนข้อความอัตโนมัติต้องใช้ข้อมูลผู้ใช้เพื่อกรอกข้อมูลในมุมมองในแอป เมื่อผู้ใช้กรอกข้อมูลในมุมมองด้วยตนเอง ระบบจะแจ้งให้บันทึกข้อมูลไปยังบริการป้อนข้อความอัตโนมัติปัจจุบัน ดังที่แสดงในรูปที่ 2
หากต้องการบันทึกข้อมูล บริการต้องระบุว่าสนใจจัดเก็บข้อมูลไว้ใช้ในอนาคต ก่อนที่ระบบ Android จะส่งคำขอบันทึกข้อมูล จะมีคำขอป้อนข้อมูลซึ่งบริการมีโอกาสป้อนข้อมูลมุมมอง หากต้องการระบุว่าสนใจที่จะบันทึกข้อมูล บริการจะใส่ออบเจ็กต์ SaveInfo
ในคำตอบสำหรับคำขอป้อนข้อมูล ออบเจ็กต์ SaveInfo
ต้องมีข้อมูลต่อไปนี้เป็นอย่างน้อย
- ประเภทข้อมูลผู้ใช้ที่บันทึกไว้ ดูรายการค่า
SAVE_DATA
ที่พร้อมใช้งานได้ที่SaveInfo
- ชุดมุมมองขั้นต่ำที่ต้องเปลี่ยนแปลงเพื่อทริกเกอร์คําขอบันทึก
เช่น แบบฟอร์มการเข้าสู่ระบบมักจะกำหนดให้ผู้ใช้อัปเดตมุมมอง
username
และpassword
เพื่อเรียกให้แสดงคำขอบันทึก
ออบเจ็กต์ SaveInfo
จะเชื่อมโยงกับออบเจ็กต์ FillResponse
ดังที่แสดงในตัวอย่างโค้ดต่อไปนี้
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(); ... }
บริการป้อนข้อความอัตโนมัติสามารถใช้ตรรกะเพื่อเก็บข้อมูลผู้ใช้ไว้ในเมธอด onSaveRequest()
ซึ่งมักจะเรียกใช้หลังจากกิจกรรมของไคลเอ็นต์เสร็จสิ้นหรือเมื่อแอปไคลเอ็นต์เรียก commit()
โค้ดต่อไปนี้แสดงตัวอย่างเมธอด 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(); }
บริการป้อนข้อความอัตโนมัติต้องเข้ารหัสข้อมูลที่ละเอียดอ่อนก่อนที่จะเก็บไว้ อย่างไรก็ตาม ข้อมูลผู้ใช้อาจรวมถึงป้ายกำกับหรือข้อมูลที่ไม่ใช่ข้อมูลที่ละเอียดอ่อน เช่น บัญชีผู้ใช้อาจมีป้ายกำกับที่ทําเครื่องหมายข้อมูลว่าเป็นบัญชีงานหรือส่วนตัว บริการต้องไม่เข้ารหัสป้ายกำกับ การไม่เข้ารหัสป้ายกำกับจะทำให้บริการต่างๆ ใช้ป้ายกำกับในมุมมองการนำเสนอได้หากผู้ใช้ไม่ได้ตรวจสอบสิทธิ์ จากนั้นบริการจะแทนที่ป้ายกำกับด้วยข้อมูลจริงได้หลังจากที่ผู้ใช้ตรวจสอบสิทธิ์แล้ว
เลื่อน UI การบันทึกการป้อนข้อความอัตโนมัติ
ตั้งแต่ Android 10 เป็นต้นไป หากคุณใช้หน้าจอหลายหน้าจอเพื่อใช้เวิร์กโฟลว์การป้อนข้อความอัตโนมัติ เช่น หน้าจอหนึ่งสำหรับช่องชื่อผู้ใช้และอีกหน้าจอสำหรับรหัสผ่าน คุณสามารถเลื่อนเวลาการบันทึก UI การป้อนข้อความอัตโนมัติได้โดยใช้ Flag SaveInfo.FLAG_DELAY_SAVE
หากตั้งค่า Flag นี้ ระบบจะไม่ทริกเกอร์ UI บันทึกการป้อนข้อความอัตโนมัติเมื่อมีการบันทึกบริบทการป้อนข้อความอัตโนมัติที่เชื่อมโยงกับการตอบกลับ SaveInfo
แต่คุณสามารถใช้กิจกรรมแยกต่างหากภายในงานเดียวกันเพื่อส่งคําขอการบรรจุในอนาคต แล้วแสดง UI ผ่านคําขอบันทึก ดูข้อมูลเพิ่มเติมได้ที่ SaveInfo.FLAG_DELAY_SAVE
ต้องมีการตรวจสอบสิทธิ์ผู้ใช้
บริการป้อนข้อความอัตโนมัติจะเพิ่มความปลอดภัยอีกระดับโดยกำหนดให้ผู้ใช้ตรวจสอบสิทธิ์ก่อนจึงจะป้อนข้อมูลในมุมมองได้ สถานการณ์ต่อไปนี้เหมาะที่จะใช้การตรวจสอบสิทธิ์ผู้ใช้
- ข้อมูลผู้ใช้ในแอปต้องปลดล็อกโดยใช้รหัสผ่านหลักหรือการสแกนลายนิ้วมือ
- คุณต้องปลดล็อกชุดข้อมูลที่ต้องการ เช่น รายละเอียดบัตรเครดิต โดยใช้รหัสยืนยันบัตร (CVC)
ในกรณีที่บริการกำหนดให้ต้องตรวจสอบสิทธิ์ผู้ใช้ก่อนปลดล็อกข้อมูล บริการจะแสดงข้อมูลเทมเพลตหรือป้ายกำกับและระบุ Intent
ที่จัดการการตรวจสอบสิทธิ์ได้ หากต้องการข้อมูลเพิ่มเติมเพื่อประมวลผลคำขอหลังจากทำตามขั้นตอนการตรวจสอบสิทธิ์เสร็จแล้ว คุณสามารถเพิ่มข้อมูลดังกล่าวลงใน Intent ได้ จากนั้นกิจกรรมการตรวจสอบสิทธิ์จะส่งข้อมูลกลับไปยังAutofillService
คลาส
ในแอปของคุณได้
ตัวอย่างโค้ดต่อไปนี้แสดงวิธีระบุว่าคําขอต้องได้รับการตรวจสอบสิทธิ์
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();
เมื่อกิจกรรมทำตามขั้นตอนการตรวจสอบสิทธิ์เสร็จแล้ว กิจกรรมจะต้องเรียกใช้เมธอด setResult()
โดยส่งค่า RESULT_OK
และตั้งค่า EXTRA_AUTHENTICATION_RESULT
เพิ่มเติมให้กับออบเจ็กต์ FillResponse
ที่มีชุดข้อมูลที่สร้างขึ้น โค้ดต่อไปนี้แสดงตัวอย่างวิธีแสดงผลลัพธ์เมื่อขั้นตอนการตรวจสอบสิทธิ์เสร็จสมบูรณ์
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);
ในกรณีที่ต้องปลดล็อกชุดข้อมูลบัตรเครดิต บริการจะแสดง UI ที่ขอ CVC คุณสามารถซ่อนข้อมูลไว้จนกว่าจะปลดล็อกชุดข้อมูลได้โดยแสดงข้อมูลสำเร็จรูป เช่น ชื่อธนาคารและตัวเลข 4 หลักสุดท้ายของหมายเลขบัตรเครดิต ตัวอย่างต่อไปนี้แสดงวิธีกำหนดให้ต้องตรวจสอบสิทธิ์สำหรับชุดข้อมูลและซ่อนข้อมูลจนกว่าผู้ใช้จะระบุ 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();
เมื่อกิจกรรมตรวจสอบ CVC แล้ว ก็ควรเรียกใช้เมธอด setResult()
โดยส่งค่า RESULT_OK
และตั้งค่า EXTRA_AUTHENTICATION_RESULT
extra เป็นออบเจ็กต์ Dataset
ที่มีหมายเลขบัตรเครดิตและวันที่หมดอายุ ชุดข้อมูลใหม่จะแทนที่ชุดข้อมูลที่ต้องตรวจสอบสิทธิ์ และระบบจะป้อนข้อมูลพร็อพเพอร์ตี้ทันที โค้ดต่อไปนี้แสดงตัวอย่างวิธีแสดงผลชุดข้อมูลเมื่อผู้ใช้ระบุ 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);
จัดระเบียบข้อมูลเป็นกลุ่มที่สมเหตุสมผล
บริการป้อนข้อความอัตโนมัติต้องจัดระเบียบข้อมูลเป็นกลุ่มตามตรรกะซึ่งแยกแนวคิดออกจากโดเมนต่างๆ ในหน้านี้ กลุ่มเชิงตรรกะเหล่านี้เรียกว่าพาร์ติชัน รายการต่อไปนี้แสดงตัวอย่างทั่วไปของพาร์ติชันและช่อง
- ข้อมูลเข้าสู่ระบบ ซึ่งรวมถึงช่องชื่อผู้ใช้และรหัสผ่าน
- ที่อยู่ ซึ่งประกอบด้วยช่องถนน เมือง รัฐ และรหัสไปรษณีย์
- ข้อมูลการชำระเงิน ซึ่งรวมถึงช่องหมายเลขบัตรเครดิต วันที่หมดอายุ และรหัสยืนยัน
บริการป้อนข้อความอัตโนมัติที่แบ่งพาร์ติชันข้อมูลอย่างถูกต้องจะปกป้องข้อมูลของผู้ใช้ได้ดียิ่งขึ้น โดยไม่เปิดเผยข้อมูลจากพาร์ติชันมากกว่า 1 รายการในชุดข้อมูล เช่น ชุดข้อมูลที่มีข้อมูลเข้าสู่ระบบไม่จำเป็นต้องมีข้อมูลการชำระเงิน การจัดระเบียบข้อมูลในพาร์ติชันช่วยให้บริการของคุณแสดงข้อมูลที่เกี่ยวข้องในปริมาณขั้นต่ำที่จำเป็นเพื่อตอบสนองคำขอได้
การจัดระเบียบข้อมูลในพาร์ติชันช่วยให้บริการสามารถป้อนข้อมูลในกิจกรรมที่มีมุมมองจากหลายพาร์ติชันขณะส่งข้อมูลที่เกี่ยวข้องไปยังแอปไคลเอ็นต์ในปริมาณที่น้อยที่สุดได้ ตัวอย่างเช่น พิจารณากิจกรรมที่มีมุมมองสำหรับชื่อผู้ใช้ รหัสผ่าน ถนน และเมือง รวมถึงบริการป้อนข้อความอัตโนมัติที่มีข้อมูลต่อไปนี้
พาร์ติชัน | ฟิลด์ 1 | ฟิลด์ 2 |
---|---|---|
ข้อมูลรับรอง | work_username | work_password |
personal_username | personal_password | |
ที่อยู่ | work_street | work_city |
personal_street | personal_city |
บริการนี้สามารถเตรียมชุดข้อมูลที่มีพาร์ติชันข้อมูลเข้าสู่ระบบสำหรับทั้งบัญชีงานและบัญชีส่วนตัว เมื่อผู้ใช้เลือกชุดข้อมูลแล้ว การตอบกลับที่ป้อนข้อความอัตโนมัติครั้งถัดไปอาจแสดงที่อยู่งานหรือที่อยู่ส่วนตัว ทั้งนี้ขึ้นอยู่กับตัวเลือกแรกของผู้ใช้
บริการจะระบุช่องที่เป็นต้นทางของคําขอได้โดยเรียกใช้เมธอด isFocused()
ขณะที่ไปยังออบเจ็กต์ AssistStructure
ซึ่งช่วยให้บริการเตรียม FillResponse
ที่มีข้อมูลพาร์ติชันที่เหมาะสมได้
ป้อนรหัส SMS แบบใช้ครั้งเดียวโดยอัตโนมัติ
บริการป้อนข้อความอัตโนมัติสามารถช่วยผู้ใช้ในการกรอกรหัสแบบครั้งเดียวที่ส่งทาง SMS โดยใช้ SMS Retriever API
คุณต้องมีคุณสมบัติตรงตามข้อกำหนดต่อไปนี้จึงจะใช้ฟีเจอร์นี้ได้
- บริการป้อนข้อความอัตโนมัติทำงานใน Android 9 (API ระดับ 28) ขึ้นไป
- ผู้ใช้ให้ความยินยอมให้บริการป้อนข้อความอัตโนมัติอ่านรหัสแบบครั้งเดียวจาก SMS
- แอปพลิเคชันที่คุณป้อนข้อความอัตโนมัติให้ไม่ได้ใช้ SMS Retriever API เพื่ออ่านรหัสแบบครั้งเดียว
บริการป้อนข้อความอัตโนมัติสามารถใช้ SmsCodeAutofillClient
ซึ่งพร้อมใช้งานโดยเรียกใช้ SmsCodeRetriever.getAutofillClient()
จากบริการ Google Play 19.0.56 ขึ้นไป
ขั้นตอนหลักในการใช้ API นี้ในบริการป้อนข้อความอัตโนมัติมีดังนี้
- ในบริการป้อนข้อความอัตโนมัติ ให้ใช้
hasOngoingSmsRequest
จากSmsCodeAutofillClient
เพื่อดูว่ามีคำขอที่ใช้งานอยู่สำหรับชื่อแพ็กเกจของแอปพลิเคชันที่คุณกรอกข้อความอัตโนมัติอยู่หรือไม่ บริการป้อนข้อความอัตโนมัติของคุณต้องแสดงข้อความแจ้งคำแนะนำก็ต่อเมื่อค่านี้แสดงผลเป็นfalse
- ในบริการป้อนข้อความอัตโนมัติ ให้ใช้
checkPermissionState
จากSmsCodeAutofillClient
เพื่อตรวจสอบว่าบริการป้อนข้อความอัตโนมัติมีสิทธิ์ป้อนรหัสแบบครั้งเดียวโดยอัตโนมัติหรือไม่ สถานะสิทธิ์นี้อาจเป็นNONE
,GRANTED
หรือDENIED
บริการป้อนข้อความอัตโนมัติต้องแสดงข้อความแจ้งคำแนะนำสำหรับรัฐNONE
และGRANTED
- ในกิจกรรมการตรวจสอบสิทธิ์แบบป้อนข้อความอัตโนมัติ ให้ใช้สิทธิ์
SmsRetriever.SEND_PERMISSION
เพื่อลงทะเบียนBroadcastReceiver
ที่รอรับSmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION
เพื่อรับผลลัพธ์รหัส SMS เมื่อพร้อมใช้งาน โทรหา
startSmsCodeRetriever
ในSmsCodeAutofillClient
เพื่อเริ่มฟังรหัสแบบใช้ครั้งเดียวที่ส่งทาง SMS หากผู้ใช้ให้สิทธิ์บริการป้อนข้อความอัตโนมัติของคุณในการดึงรหัสแบบครั้งเดียวจาก SMS ระบบจะค้นหาข้อความ SMS ที่ได้รับในช่วง 1-5 นาทีที่ผ่านมาหากบริการป้อนข้อความอัตโนมัติต้องขอสิทธิ์จากผู้ใช้เพื่ออ่านรหัสแบบครั้งเดียว
Task
ที่startSmsCodeRetriever
แสดงอาจไม่สำเร็จและแสดงResolvableApiException
ในกรณีนี้ คุณต้องเรียกใช้เมธอดResolvableApiException.startResolutionForResult()
เพื่อแสดงกล่องโต้ตอบความยินยอมสําหรับคําขอสิทธิ์รับผลลัพธ์รหัส SMS จาก Intent แล้วแสดงรหัส SMS เป็นการตอบกลับแบบป้อนข้อความอัตโนมัติ
สถานการณ์การป้อนข้อความอัตโนมัติขั้นสูง
- ผสานรวมกับแป้นพิมพ์
- ตั้งแต่ Android 11 เป็นต้นไป แพลตฟอร์มจะอนุญาตให้แป้นพิมพ์และเครื่องมือแก้ไขวิธีป้อนข้อมูลอื่นๆ (IME) แสดงคำแนะนำการป้อนข้อความอัตโนมัติในบรรทัดแทนการใช้เมนูแบบเลื่อนลง ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่บริการป้อนข้อความอัตโนมัติรองรับฟังก์ชันการทำงานนี้ได้ที่หัวข้อผสานรวมการป้อนข้อความอัตโนมัติกับแป้นพิมพ์
- จัดชุดข้อมูลเป็นหน้า
- คำตอบการเติมข้อความอัตโนมัติขนาดใหญ่อาจเกินขนาดธุรกรรมที่อนุญาตของออบเจ็กต์
Binder
ที่แสดงถึงออบเจ็กต์ระยะไกลที่จําเป็นสําหรับประมวลผลคําขอ หากต้องการป้องกันไม่ให้ระบบ Android แสดงข้อยกเว้นในสถานการณ์เหล่านี้ คุณสามารถทำให้FillResponse
มีขนาดไม่ใหญ่โดยการเพิ่มออบเจ็กต์Dataset
ไม่เกิน 20 รายการในแต่ละครั้ง หากคำตอบต้องใช้ชุดข้อมูลเพิ่ม คุณสามารถเพิ่มชุดข้อมูลที่จะแจ้งให้ผู้ใช้ทราบว่ามีข้อมูลเพิ่มเติมและดึงชุดข้อมูลกลุ่มถัดไปเมื่อเลือก ดูข้อมูลเพิ่มเติมได้ที่addDataset(Dataset)
- บันทึกข้อมูลที่แยกออกเป็นหลายหน้าจอ
แอปมักแยกข้อมูลผู้ใช้ในหลายหน้าจอในกิจกรรมเดียวกัน โดยเฉพาะในกิจกรรมที่ใช้สร้างบัญชีผู้ใช้ใหม่ เช่น หน้าจอแรกจะขอชื่อผู้ใช้ และหากชื่อผู้ใช้นั้นใช้ได้ หน้าจอที่ 2 จะขอรหัสผ่าน ในกรณีเหล่านี้ บริการป้อนข้อความอัตโนมัติต้องรอจนกว่าผู้ใช้จะป้อนข้อมูลในทั้ง 2 ช่องก่อนจึงจะแสดง UI บันทึกข้อความอัตโนมัติได้ ทําตามขั้นตอนต่อไปนี้เพื่อจัดการสถานการณ์ดังกล่าว
- ในคำขอกรอกข้อมูลแรก ให้เพิ่มกลุ่มสถานะไคลเอ็นต์ในการตอบกลับซึ่งมีรหัสป้อนข้อความอัตโนมัติของฟิลด์บางส่วนที่แสดงในหน้าจอ
- ในคำขอป้อนข้อมูลครั้งที่ 2 ให้ดึงข้อมูลกลุ่มสถานะไคลเอ็นต์ รับรหัสป้อนอัตโนมัติที่ตั้งค่าไว้ในคำขอก่อนหน้าจากสถานะไคลเอ็นต์ และเพิ่มรหัสเหล่านี้และธง
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
ลงในออบเจ็กต์SaveInfo
ที่ใช้ในการตอบกลับครั้งที่ 2 - ในคําขอบันทึก ให้ใช้ออบเจ็กต์
FillContext
ที่เหมาะสมเพื่อรับค่าของแต่ละช่อง บริบทการโฆษณาจะอยู่ที่ 1 รายการต่อคำขอโฆษณา
ดูข้อมูลเพิ่มเติมได้ที่บันทึกเมื่อข้อมูลมีการแยกออกเป็นหลายหน้าจอ
- ระบุตรรกะการเริ่มต้นและการสร้างใหม่สำหรับคำขอแต่ละรายการ
ทุกครั้งที่มีคำขอป้อนข้อความอัตโนมัติ ระบบ Android จะเชื่อมโยงกับบริการและเรียกใช้เมธอด
onConnected()
เมื่อบริการประมวลผลคําขอแล้ว ระบบ Android จะเรียกใช้เมธอดonDisconnected()
และยกเลิกการเชื่อมโยงกับบริการ คุณสามารถใช้onConnected()
เพื่อระบุโค้ดที่จะทำงานก่อนประมวลผลคำขอ และonDisconnected()
เพื่อระบุโค้ดที่จะทำงานหลังจากประมวลผลคำขอ- ปรับแต่ง UI การบันทึกการป้อนข้อความอัตโนมัติ
บริการป้อนข้อความอัตโนมัติสามารถปรับแต่ง UI ของการเก็บข้อมูลป้อนข้อความอัตโนมัติเพื่อช่วยผู้ใช้ตัดสินใจว่าต้องการอนุญาตให้บริการบันทึกข้อมูลหรือไม่ บริการสามารถให้ข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่บันทึกไว้ผ่านข้อความธรรมดาหรือผ่านมุมมองที่กำหนดเอง บริการยังเปลี่ยนลักษณะที่ปรากฏของปุ่มที่ยกเลิกคำขอบันทึกและรับการแจ้งเตือนเมื่อผู้ใช้แตะปุ่มดังกล่าวได้ด้วย ดูข้อมูลเพิ่มเติมได้ที่หน้าอ้างอิง
SaveInfo
- โหมดความเข้ากันได้
โหมดความเข้ากันได้ช่วยให้บริการป้อนข้อความอัตโนมัติใช้โครงสร้างเสมือนจริงสำหรับการช่วยเหลือพิเศษเพื่อวัตถุประสงค์ในการป้อนข้อความอัตโนมัติได้ ซึ่งมีประโยชน์อย่างยิ่งในการระบุฟังก์ชันการป้อนข้อความอัตโนมัติในเบราว์เซอร์ที่ไม่ได้ใช้ API การป้อนข้อความอัตโนมัติอย่างชัดเจน
หากต้องการทดสอบบริการป้อนข้อความอัตโนมัติโดยใช้โหมดความเข้ากันได้ ให้เพิ่มเบราว์เซอร์หรือแอปที่ต้องใช้โหมดความเข้ากันได้ลงในรายการที่อนุญาตอย่างชัดเจน คุณสามารถตรวจสอบว่าแพ็กเกจใดอยู่ในรายการที่อนุญาตแล้วโดยเรียกใช้คําสั่งต่อไปนี้
$ adb shell settings get global autofill_compat_mode_allowed_packages
หากแพ็กเกจที่คุณทดสอบไม่อยู่ในรายการ ให้เพิ่มโดยเรียกใช้คำสั่งต่อไปนี้ โดยที่
pkgX
คือแพ็กเกจของแอป$ adb shell settings put global autofill_compat_mode_allowed_packages pkg1[resId1]:pkg2[resId1,resId2]
หากแอปเป็นเบราว์เซอร์ ให้ใช้
resIdx
เพื่อระบุรหัสทรัพยากรของช่องป้อนข้อมูลที่มี URL ของหน้าที่แสดงผล
โหมดความเข้ากันได้มีข้อจํากัดต่อไปนี้
- คำขอบันทึกจะทริกเกอร์เมื่อบริการใช้ Flag
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
หรือเรียกใช้เมธอดsetTrigger()
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
จะถูกตั้งค่าไว้โดยค่าเริ่มต้นเมื่อใช้โหมดความเข้ากันได้ - ค่าข้อความของโหนดอาจไม่พร้อมใช้งานในวิธี
onSaveRequest(SaveRequest, SaveCallback)
ดูข้อมูลเพิ่มเติมเกี่ยวกับโหมดความเข้ากันได้ รวมถึงข้อจำกัดที่เกี่ยวข้องได้ที่ข้อมูลอ้างอิงคลาส AutofillService