자동 완성 서비스는 사용자가 양식을 쉽게 작성할 수 있도록 지원하는 앱으로, 이를 위해 다른 앱의 뷰에 데이터를 입력하는 방식을 사용합니다. 자동 완성 서비스는 앱의 뷰에서 사용자 데이터를 가져오고 나중에 사용하기 위해 저장할 수도 있습니다. 자동 완성 서비스는 일반적으로 사용자 데이터를 관리하는 앱(예: 비밀번호 관리자)이 제공합니다.
Android는 Android 8.0(API 레벨 26) 이상에서 제공되는 자동 완성 프레임워크로 쉽게 양식을 작성할 수 있습니다. 사용자는 기기에 자동 완성 서비스를 제공하는 앱이 있을 때만 자동 완성 기능을 사용할 수 있습니다.
이 페이지에서는 앱에 자동 완성 서비스를 구현하는 방법을 보여줍니다. 서비스를 구현하는 방법을 보여주는 코드 샘플을 찾고 있다면 Android AutofillFramework 샘플을 참조하세요. 자동 완성 서비스의 작동 방법에 대한 자세한 내용은 AutofillService
및AutofillManager
클래스의 참고 문서를 참조하세요.
매니페스트 선언 및 권한
자동 완성 서비스를 제공하는 앱에는 서비스의 구현에 대해 설명하는 선언을 포함해야 합니다. 이 선언을 지정하려면 앱 매니페스트에 <service>
요소를 넣습니다. <service>
요소는 다음 특성과 요소를 포함해야 합니다.
- 서비스를 구현하는 앱에서
AutofillService
의 하위 클래스를 가리키는android:name
특성. BIND_AUTOFILL_SERVICE
권한을 선언하는android:permission
특성.<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>
요소에는 서비스에 대한 추가적인 세부사항을 포함한 XML 리소스를 가리키는 android:resource
특성이 있습니다. 앞의 예시에서 service_configuration
리소스는 사용자가 서비스를 구성하도록 허용하는 Activity를 지정합니다. 다음 예시는 service_configuration
XML 리소스를 나타냅니다.
<autofill-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.example.android.SettingsActivity" />
XML 리소스에 대한 자세한 내용은 리소스 제공을 참조하세요.
서비스 활성화를 위한 프롬프트
앱을 자동 완성 서비스로 사용하려면 앱이 BIND_AUTOFILL_SERVICE
권한을 선언하고, 사용자가 기기 설정에서 이를 활성화해야 합니다. 앱이 현재 활성화된 서비스인지 확인하려면 AutofillManager
클래스의 hasEnabledAutofillServices()
메서드를 호출하면 됩니다.
앱이 현재 자동 완성 서비스가 아닌 경우, ACTION_REQUEST_SET_AUTOFILL_SERVICE
인텐트를 사용하여 사용자에게 자동 완성 설정을 변경하도록 요청할 수 있습니다. 인텐트는 사용자가 발신자의 패키지와 일치하는 자동 완성 서비스를 선택한 경우 RESULT_OK
값을 반환합니다.
클라이언트 뷰 작성
자동 완성 서비스는 사용자가 다른 앱과 상호작용할 때 클라이언트 뷰를 작성하라는 요청을 수신합니다. 자동 완성 서비스에 해당 요청을 만족하는 사용자 데이터가 있는 경우, 응답에 그 데이터를 담아 전송합니다. Android 시스템은 그림 1과 같이 이용 가능한 데이터를 포함한 자동 완성 UI를 표시합니다.
그림 1. 데이터세트를 표시하는 자동 완성 UI.
자동 완성 프레임워크는 Android 시스템이 자동 완성 서비스에 바인딩되는 시간을 최소화하도록 설계된 뷰를 작성하는 워크플로를 정의합니다. Android 시스템은 요청을 보낼 때마다 onFillRequest()
메서드를 호출해 AssistStructure
객체를 서비스로 전송합니다. 자동 완성 서비스는 이전에 저장해둔 사용자 데이터로 요청에 부응할 수 있는지 확인합니다. 요청에 부응할 수 있는 경우, 서비스는 해당 데이터를 Dataset
객체에 패키징합니다. 서비스가 onSuccess()
메서드를 호출하면서 FillResponse
객체를 전달하는데, 이 객체에는 Dataset
객체가 포함됩니다. 서비스가 요청에 부응할 데이터를 가지고 있지 않은 경우, 서비스는 onSuccess()
메서드에 null
을 전달합니다. 요청을 처리하면서 오류가 발생하면 대신 서비스가 onFailure()
메서드를 호출합니다. 워크플로에 대한 자세한 내용은 기본 사용법을 참조하세요.
다음 코드는 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; }
한 서비스에 요청에 부응하는 데이터세트가 두 개 이상 있을 수 있습니다. 이 경우, Android 시스템은 자동 완성 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();
자동 완성 서비스는 AssistStructure
에서 ViewNode
객체를 탐색해 요청을 수행하는 데 필요한 자동 완성 데이터를 검색할 수 있습니다. 서비스는 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와 같이 현재 자동 완성 서비스에 데이터를 저장하라는 메시지가 표시됩니다.
그림 2. 자동 완성 저장 UI
데이터를 저장하려면 서비스가 나중을 위해 데이터를 저장하고자 한다는 의도를 표시해야 합니다. 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()
메서드에서 사용자 데이터를 유지하기 위해 로직을 구현할 수 있습니다. 이는 대체로 클라이언트 Activity가 종료된 후 또는 클라이언트 앱이 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부터는 여러 화면을 사용하여 자동 완성 워크플로를 구현할 경우(예: 사용자 이름 필드를 위한 화면 1개와 비밀번호를 위한 화면 1개), SaveInfo.FLAG_DELAY_SAVE
플래그를 사용하여 자동 완성 저장 UI를 연기할 수 있습니다.
이 플래그가 설정되면 SaveInfo
응답과 연결된 자동 완성 컨텍스트가 커밋되었을 때 자동 완성 저장 UI가 트리거되지 않습니다. 대신, 같은 작업 내에 있는 별도의 Activity를 사용하여 나중에 작성 요청을 전달한 다음 저장 요청을 통해 UI를 표시할 수 있습니다. 자세한 내용은 SaveInfo.FLAG_DELAY_SAVE
를 참조하세요.
사용자 인증 필요
자동 완성 서비스는 뷰를 작성하기 전에 사용자가 인증해야 하도록 함으로써 추가적인 수준의 보안을 제공할 수 있습니다. 다음 시나리오는 사용자 인증을 구현하는 데 좋은 방법입니다.
- 앱 내의 사용자 데이터는 마스터 비밀번호 또는 지문 스캔을 사용하여 해제해야 합니다.
- 특정 데이터세트를 잠금 해제해야 하는 경우, 예를 들어 신용 카드 세부사항은 카드 인증 코드(CVC)를 사용합니다.
서비스가 데이터를 잠금 해제하기 전에 사용자 인증이 필요한 경우, 서비스는 표준 문안 데이터 또는 라벨을 표시한 다음 인증을 해결할 Intent
를 지정할 수 있습니다. 인증 플로가 완료된 뒤에 요청을 처리하기 위해 추가적인 데이터가 필요한 경우, 그런 데이터를 해당 인텐트에 추가하면 됩니다. 그러면 인증 Activity가 앱의 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();
Activity가 인증 플로를 완료하고 나면 setResult()
메서드를 호출해 RESULT_OK
값을 전달하고 작성된 데이터세트가 포함된 FillResponse
객체로 EXTRA_AUTHENTICATION_RESULT
엑스트라를 설정해야 합니다. 다음 코드는 인증 플로가 완료된 후 결과를 반환하는 방법을 보여줍니다.
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);
신용카드 데이터세트를 잠금 해제해야 하는 시나리오에서 서비스는 CVC를 요청하는 UI를 표시할 수 있습니다. 데이터세트가 잠금 해제될 때까지 표준 문안 데이터(예: 은행 이름, 신용카드 번호의 마지막 네 자리)를 표시하여 데이터를 숨길 수 있습니다. 다음 예시는 데이터세트에 대한 인증을 요구하며 사용자가 CVC를 제공할 때까지 데이터를 숨기는 방법을 보여줍니다.
Kotlin
// Parse the structure and fetch payment data. val parsedStructure: ParsedStructure = parseStructure(structure) val paymentData: Payment = fetchPaymentData(parsedStructure) // Build the presentation that shows the bank and the last four digits of the credit card number. // For example 'Bank-1234'. val maskedPresentation: String = "${paymentData.bank}-" + paymentData.creditCardNumber.substring(paymentData.creditCardNumber.length - 4) val authPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply { setTextViewText(android.R.id.text1, maskedPresentation) } // Prepare an intent that displays the UI that asks for the CVC. val cvcIntent = Intent(this, CvcActivity::class.java) val cvcIntentSender: IntentSender = PendingIntent.getActivity( this, 1001, cvcIntent, PendingIntent.FLAG_CANCEL_CURRENT ).intentSender // Build a FillResponse object that includes a Dataset that requires authentication. val fillResponse: FillResponse = FillResponse.Builder() .addDataset( Dataset.Builder() // The values in the dataset are replaced by the actual // data once the user provides the CVC. .setValue(parsedStructure.creditCardId, null, authPresentation) .setValue(parsedStructure.expDateId, null, authPresentation) .setAuthentication(cvcIntentSender) .build() ).build()
Java
// Parse the structure and fetch payment data. ParsedStructure parsedStructure = parseStructure(structure); Payment paymentData = fetchPaymentData(parsedStructure); // Build the presentation that shows the bank and the last four digits of the credit card number. // For example 'Bank-1234'. String maskedPresentation = paymentData.bank + "-" + paymentData.creditCardNumber.subString(paymentData.creditCardNumber.length - 4); RemoteViews authPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1); authPresentation.setTextViewText(android.R.id.text1, maskedPresentation); // Prepare an intent that displays the UI that asks for the CVC. Intent cvcIntent = new Intent(this, CvcActivity.class); IntentSender cvcIntentSender = PendingIntent.getActivity( this, 1001, cvcIntent, PendingIntent.FLAG_CANCEL_CURRENT ).getIntentSender(); // Build a FillResponse object that includes a Dataset that requires authentication. FillResponse fillResponse = new FillResponse.Builder() .addDataset(new Dataset.Builder() // The values in the dataset are replaced by the actual // data once the user provides the CVC. .setValue(parsedStructure.creditCardId, null, authPresentation) .setValue(parsedStructure.expDateId, null, authPresentation) .setAuthentication(cvcIntentSender) .build()) .build();
Activity가 CVC를 인증하고 나면 setResult()
메서드를 호출해 RESULT_OK
값을 전달하고 EXTRA_AUTHENTICATION_RESULT
엑스트라를 신용카드 번호와 만료일이 포함된 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 that we can use as the presentation when // creating the Dataset object. This presentation isn't actually used, but the // Builder object requires a non-null presentation. val notUsed = RemoteViews(packageName, android.R.layout.simple_list_item_1) // Create a dataset with the credit card number and expiration date. val responseDataset: Dataset = Dataset.Builder() .setValue( parsedStructure.creditCardId, AutofillValue.forText(paymentData.creditCardNumber), notUsed ) .setValue( parsedStructure.expDateId, AutofillValue.forText(paymentData.expirationDate), notUsed ) .build() val replyIntent = Intent().apply { putExtra(EXTRA_AUTHENTICATION_RESULT, responseDataset) }
Java
// Parse the structure and fetch payment data. ParsedStructure parsedStructure = parseStructure(structure); Payment paymentData = fetchPaymentData(parsedStructure); // Build a non-null RemoteViews object that we can use as the presentation when // creating the Dataset object. This presentation isn't actually used, but the // Builder object requires a non-null presentation. RemoteViews notUsed = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1); // Create a dataset with the credit card number and expiration date. Dataset responseDataset = new Dataset.Builder() .setValue(parsedStructure.creditCardId, AutofillValue.forText(paymentData.creditCardNumber), notUsed) .setValue(parsedStructure.expDateId, AutofillValue.forText(paymentData.expirationDate), notUsed) .build(); Intent replyIntent = new Intent(); replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, responseDataset);
논리적 그룹에서 데이터 정리
자동 완성 서비스는 데이터를 논리적 그룹으로 정리해 개념을 다른 도메인과 분리해야 합니다. 이 페이지에서는 이런 논리적 그룹을 파티션이라고 부릅니다. 다음 목록은 파티션과 필드의 일반적 예시를 나타냅니다.
- 사용자 인증 정보. 사용자 이름과 비밀번호 필드가 포함됩니다.
- 주소. 도로명, 구/군/시, 주 및 우편번호 필드가 포함됩니다.
- 결제 정보. 신용카드 번호, 만료일 및 인증 코드 필드가 포함됩니다.
자동 완성 서비스가 데이터를 올바로 구분하면 사용자의 데이터를 더욱 잘 보호할 수 있습니다. 데이터를 데이터세트 내 두 개 이상의 파티션에 노출하지 않기 때문입니다. 예를 들어 사용자 인증 정보를 포함하는 데이터세트에 결제 정보를 꼭 포함할 필요는 없습니다. 데이터를 파티션으로 정리하면 서비스가 요청에 부응하는 데 필요한 최소한의 정보만 노출하도록 할 수 있습니다.
데이터를 파티션으로 정리하면 서비스가 여러 개의 파티션에 뷰가 있는 Activity를 채우면서 동시에 클라이언트 앱에는 최소한의 데이터만 전송할 수 있습니다. 예를 들어 어떤 Activity에 사용자 이름, 비밀번호, 도로명과 구/군/시 뷰가 포함되어 있고 자동 완성 서비스에는 다음과 같은 데이터가 포함되어 있다고 가정합니다.
파티션 | 필드 1 | 필드 2 |
---|---|---|
사용자 인증 정보 | work_username | work_password |
personal_username | personal_password | |
주소 | work_street | work_city |
personal_street | personal_city |
서비스는 업무 및 개인 계정 모두의 사용자 인증 정보 파티션을 포함하는 데이터세트를 작성할 수 있습니다. 사용자가 하나를 선택하면, 이후의 자동 완성 응답은 사용자의 첫 번째 선택에 따라 업무 또는 개인 주소를 제공할 수 있습니다.
서비스는 AssistStructure
객체를 트래버스하는 동안 isFocused()
메서드를 호출해 요청을 보낸 필드를 식별할 수 있습니다. 이렇게 하면 서비스가 적절한 파티션 데이터로 FillResponse
를 작성할 수 있습니다.
고급 자동 완성 시나리오
- 데이터세트 페이지 번호 매김
- 대량 자동 완성 응답의 경우 요청을 처리하는 데 필요한 원격 객체를 나타내는
Binder
객체의 허용된 전송 크기를 초과할 수 있습니다. 이 시나리오에서 Android 시스템이 예외를 발생시키지 않게 하려면, 한 번에Dataset
객체를 20개 이하로 추가하여FillResponse
를 작은 크기로 유지하면 됩니다. 응답에 더 많은 데이터세트가 필요한 경우, 데이터세트 하나를 추가해 사용자가 이를 선택하면 더 많은 정보가 있다는 것을 알려주고 다음 데이터세트 그룹을 검색하도록 할 수 있습니다. 자세한 내용은addDataset(Dataset)
를 참조하세요. - 여러 화면으로 분할된 데이터 저장
앱은 동일한 Activity, 특히 새로운 사용자 계정을 생성하는 데 사용되는 Activity에서 사용자 데이터를 여러 화면으로 분할하는 경우가 많습니다. 예를 들어 첫 번째 화면에서 사용자 이름을 요청하고, 사용자 이름이 사용 가능하면 두 번째 화면으로 이동하여 비밀번호를 요청합니다. 이 경우, 자동 완성 서비스는 사용자가 두 개 필드를 모두 입력할 때까지 기다려야만 자동 완성 저장 UI를 표시할 수 있습니다. 서비스는 다음 단계에 따라 이런 시나리오를 처리할 수 있습니다.
- 첫 번째 작성 요청에서 서비스는 응답에 클라이언트 상태 번들을 추가합니다. 여기에는 화면에 표시된 부분 필드의 자동 완성 ID가 포함됩니다.
- 두 번째 작성 요청에서 서비스는 클라이언트 상태 번들을 검색하고, 클라이언트 상태의 이전 요청에서 설정된 자동 완성 ID를 가져와 이 ID와
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
플래그를 두 번째 응답에 사용된SaveInfo
객체에 추가합니다. - 저장 요청에서 서비스는 적절한
FillContext
객체를 사용해 각 필드의 값을 가져옵니다. 작성 요청 하나에 작성 컨텍스트가 하나씩 있습니다.
자세한 내용은 여러 화면으로 분할된 데이터 저장을 참조하세요.
- 각 요청에 초기화 및 해체 로직 제공
Android 시스템은 자동 완성 요청이 있을 때마다 서비스에 바인딩되고
onConnected()
메서드를 호출합니다. 서비스가 요청을 처리하고 나면 Android 시스템이onDisconnected()
메서드를 호출하고 서비스로부터 바인드를 해제합니다.onConnected()
를 구현하면 요청을 처리하기 전에 실행되는 코드를 제공하고,onDisconnected()
를 구현하면 요청을 처리한 후에 실행되는 코드를 제공할 수 있습니다.- 자동 완성 저장 UI 맞춤 설정
자동 완성 서비스는 자동 완성 저장 UI를 맞춤 설정하여 사용자로 하여금 서비스가 사용자 데이터를 저장하도록 허용할지 여부를 결정하도록 지원합니다. 서비스는 단순한 텍스트 또는 맞춤 설정된 뷰를 통해 저장될 데이터에 대한 추가 정보를 제공할 수 있습니다. 또한 서비스는 저장 요청을 취소하는 버튼의 모양을 변경하고 사용자가 그 버튼을 탭하면 알림을 받을 수 있습니다. 자세한 내용은
SaveInfo
참고 문서를 참조하세요.- 호환성 모드
호환성 모드에서는 자동 완성 서비스가 자동 완성 용도로 접근성 가상 구조를 사용할 수 있습니다. 이는 아직 자동 완성 API를 명시적으로 구현하지 않은 브라우저에서 자동 완성 기능을 제공하는 데 매우 유용합니다.
호환성 모드를 사용하여 자동 완성 서비스를 테스트하려면 호환성 모드를 필요로 하는 브라우저 또는 앱을 명시적으로 허용해야 합니다. 다음 명령을 실행하면 이미 허용된 패키지 목록을 확인할 수 있습니다.
$ adb shell settings get global autofill_compat_mode_allowed_packages
테스트하는 패키지가 목록에 없는 경우 다음과 같은 명령을 실행하여 등록할 수 있습니다.
$ adb shell settings put global autofill_compat_mode_allowed_packages pkg1[resId1]:pkg2[resId1,resId2]
...
pkgX
는 앱의 패키지입니다. 앱이 브라우저인 경우,resIdx
를 사용하면 렌더링된 페이지의 URL을 포함한 입력 필드의 리소스 ID를 지정할 수 있습니다.
호환성 모드에는 다음과 같은 제한 사항이 있습니다.
- 저장 요청은 서비스가 FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE 플래그를 사용하거나
setTrigger()
메서드를 호출했을 때 트리거됩니다.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
은 호환성 모드를 사용할 때 기본 설정됩니다. onSaveRequest(SaveRequest, SaveCallback)
메서드에서 노드의 텍스트 값을 사용하지 못할 수도 있습니다.
호환성 모드 및 그와 관련된 제한 사항에 대한 자세한 내용은 AutofillService
클래스 참고 문서를 참조하세요.