Creare servizi di compilazione automatica

Un servizio di compilazione automatica è un'app che aiuta gli utenti a compilare più facilmente i moduli inserendo dati nelle visualizzazioni di altre app. I servizi di compilazione automatica possono anche recuperare i dati utente dalle viste in un'app e archiviarli per utilizzarli in un secondo momento. I servizi di compilazione automatica vengono in genere forniti da app che gestiscono i dati utente, ad esempio i gestori delle password.

Android semplifica la compilazione dei moduli grazie al framework di compilazione automatica disponibile su Android 8.0 (livello API 26) e versioni successive. Gli utenti possono sfruttare le funzionalità di compilazione automatica solo se sul loro dispositivo è disponibile un'app che fornisce servizi di compilazione automatica.

Questa pagina mostra come implementare un servizio di compilazione automatica nella tua app. Se stai cercando un esempio di codice che mostri come implementare un servizio, consulta l'esempio di AutofillFramework in Java o Kotlin. Per ulteriori dettagli sul funzionamento dei servizi di compilazione automatica, consulta le pagine di riferimento per i corsi AutofillService e AutofillManager.

Dichiarazioni e autorizzazioni del file manifest

Le app che forniscono servizi di compilazione automatica devono includere una dichiarazione che descriva l'implementazione del servizio. Per specificare la dichiarazione, includi un elemento <service> nel file manifest dell'app. L'elemento <service> deve includere i seguenti attributi ed elementi:

  • Attributo android:name che rimanda alla sottoclasse AutofillService nell'app che implementa il servizio.
  • Attributo android:permission che dichiara l'autorizzazione BIND_AUTOFILL_SERVICE.
  • L'elemento <intent-filter> il cui elemento secondario obbligatorio <action> specifica l'azione android.service.autofill.AutofillService.
  • Elemento <meta-data> facoltativo che puoi utilizzare per fornire parametri di configurazione aggiuntivi per il servizio.

L'esempio seguente mostra una dichiarazione del servizio di compilazione automatica:

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

L'elemento <meta-data> include un attributo android:resource che rimanda a una risorsa XML con ulteriori dettagli sul servizio. La risorsa service_configuration nell'esempio precedente specifica un'attività che consente agli utenti di configurare il servizio. L'esempio seguente mostra la risorsa XML service_configuration:

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

Per ulteriori informazioni sulle risorse XML, consulta la panoramica delle risorse per le app.

Richiesta di abilitazione del servizio

Un'app viene utilizzata come servizio di compilazione automatica dopo aver dichiarato l'autorizzazione BIND_AUTOFILL_SERVICE e dopo che l'utente l'ha abilitata nelle impostazioni del dispositivo. Un'app può verificare se è il servizio attualmente abilitato chiamando il metodo hasEnabledAutofillServices() della classe AutofillManager.

Se l'app non è l'attuale servizio di compilazione automatica, può richiedere all'utente di modificare le impostazioni di compilazione automatica utilizzando l'intent ACTION_REQUEST_SET_AUTOFILL_SERVICE. L'intent restituisce un valore pari a RESULT_OK se l'utente seleziona un servizio di compilazione automatica che corrisponde al pacchetto del chiamante.

Compila le visualizzazioni dei clienti

Il servizio di compilazione automatica riceve richieste di compilazione delle viste del client quando l'utente interagisce con altre app. Se il servizio di compilazione automatica dispone di dati utente che soddisfano la richiesta, invia i dati nella risposta. Il sistema Android mostra un'interfaccia utente di compilazione automatica con i dati disponibili, come mostrato nella Figura 1:

UI compilazione automatica

Figura 1. UI di compilazione automatica che mostra un set di dati.

Il framework di compilazione automatica definisce un flusso di lavoro per compilare le visualizzazioni, progettato per ridurre al minimo il tempo di associazione del sistema Android al servizio di compilazione automatica. In ogni richiesta, il sistema Android invia un oggetto AssistStructure al servizio chiamando il metodo onFillRequest().

Il servizio di compilazione automatica verifica se è in grado di soddisfare la richiesta con dati utente archiviati in precedenza. Se è in grado di soddisfare la richiesta, il servizio pacchettizza i dati negli oggetti Dataset. Il servizio chiama il metodo onSuccess(), passando un oggetto FillResponse contenente gli oggetti Dataset. Se il servizio non dispone di dati per soddisfare la richiesta, passa null al metodo onSuccess().

Il servizio chiama invece il metodo onFailure() se si verifica un errore durante l'elaborazione della richiesta. Per una spiegazione dettagliata del flusso di lavoro, consulta la descrizione nella pagina di riferimento AutofillService.

Il seguente codice mostra un esempio del metodo 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;
}

Un servizio può avere più di un set di dati che soddisfa la richiesta. In questo caso, il sistema Android mostra più opzioni, una per ogni set di dati, nell'interfaccia utente di compilazione automatica. Il seguente esempio di codice mostra come fornire più set di dati in una risposta:

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();

I servizi di compilazione automatica possono esplorare gli oggetti ViewNode all'interno della AssistStructure per recuperare i dati di compilazione automatica necessari per soddisfare la richiesta. Un servizio può recuperare i dati di compilazione automatica utilizzando i metodi della classe ViewNode, come getAutofillId().

Un servizio deve essere in grado di descrivere i contenuti di una vista per verificare se può soddisfare la richiesta. L'utilizzo dell'attributo autofillHints è il primo approccio che un servizio deve utilizzare per descrivere i contenuti di una vista. Tuttavia, le app client devono fornire esplicitamente l'attributo nelle loro visualizzazioni prima che sia disponibile per il servizio.

Se un'app client non fornisce l'attributo autofillHints, un servizio deve utilizzare le proprie euristiche per descrivere i contenuti. Il servizio può utilizzare metodi di altre classi, come getText() o getHint(), per ottenere informazioni sui contenuti della vista. Per ulteriori informazioni, consulta l'articolo Fornire suggerimenti per la compilazione automatica.

L'esempio seguente mostra come attraversare AssistStructure e recuperare i dati di compilazione automatica da un oggetto 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);
    }
}

Salvare i dati degli utenti

Un servizio di compilazione automatica ha bisogno dei dati utente per compilare le visualizzazioni nelle app. Quando gli utenti compilano manualmente una vista, devono salvare i dati nel servizio di compilazione automatica attuale, come mostrato nella Figura 2.

UI per il salvataggio della compilazione automatica

Figura 2. UI di salvataggio della compilazione automatica.

Per salvare i dati, il servizio deve indicare di essere interessato all'archiviazione dei dati per un utilizzo futuro. Prima che il sistema Android invii una richiesta per salvare i dati, esiste una richiesta di riempimento in cui il servizio ha la possibilità di compilare le visualizzazioni. Per indicare che è interessato a salvare i dati, il servizio include un oggetto SaveInfo nella risposta alla richiesta di riempimento. L'oggetto SaveInfo contiene almeno i seguenti dati:

  • Il tipo di dati utente salvati. Per un elenco dei valori di SAVE_DATA disponibili, vedi SaveInfo.
  • L'insieme minimo di visualizzazioni che deve essere modificato per attivare una richiesta di salvataggio. Ad esempio, un modulo di accesso in genere richiede all'utente di aggiornare le viste username e password per attivare una richiesta di salvataggio.

Un oggetto SaveInfo è associato a un oggetto FillResponse, come mostrato nell'esempio di codice seguente:

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();
    ...
}

Il servizio di compilazione automatica può implementare la logica per mantenere i dati utente nel metodo onSaveRequest(), che in genere viene chiamato al termine dell'attività del client o quando l'app client chiama commit(). Il seguente codice mostra un esempio del metodo 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();
}

I servizi di compilazione automatica devono criptare i dati sensibili prima di poterli salvare. Tuttavia, i dati utente possono includere etichette o dati non sensibili. Ad esempio, un account utente può includere un'etichetta che contrassegna i dati come account di lavoro o personale. I servizi non devono criptare le etichette. Senza la crittografia delle etichette, i servizi possono utilizzare le etichette nelle visualizzazioni della presentazione se l'utente non ha eseguito l'autenticazione. Quindi, i servizi possono sostituire le etichette con i dati effettivi dopo l'autenticazione dell'utente.

Rimanda l'interfaccia utente di salvataggio della compilazione automatica

A partire da Android 10, se utilizzi più schermate per implementare un flusso di lavoro di compilazione automatica, ad esempio una schermata per il campo del nome utente e un'altra per la password, puoi posticipare l'interfaccia utente di salvataggio della compilazione automatica utilizzando il flag SaveInfo.FLAG_DELAY_SAVE.

Se questo flag è impostato, l'interfaccia utente di salvataggio della compilazione automatica non viene attivata quando viene eseguito il commit del contesto di compilazione automatica associato alla risposta SaveInfo. Puoi invece utilizzare un'attività separata all'interno della stessa attività per inviare richieste di riempimento future e quindi mostrare l'interfaccia utente tramite una richiesta di salvataggio. Per maggiori informazioni, consulta SaveInfo.FLAG_DELAY_SAVE.

Richiedi autenticazione utente

I servizi di compilazione automatica possono fornire un ulteriore livello di sicurezza, richiedendo all'utente di eseguire l'autenticazione prima di poter completare le visualizzazioni. I seguenti scenari sono ottimi candidati per implementare l'autenticazione utente:

  • I dati utente nell'app devono essere sbloccati utilizzando una password principale o una scansione delle impronte.
  • È necessario sbloccare un set di dati specifico, ad esempio i dati della carta di credito, tramite un codice di verifica della carta (CVC).

In un scenario in cui il servizio richiede l'autenticazione dell'utente prima di sbloccare i dati, può presentare dati boilerplate o un'etichetta e specificare il Intent che si occupa dell'autenticazione. Se hai bisogno di ulteriori dati per elaborare la richiesta dopo il completamento del flusso di autenticazione, puoi aggiungere questi dati all'intent. La tua attività di autenticazione può quindi restituire i dati alla classe AutofillService nella tua app.

Il seguente esempio di codice mostra come specificare che la richiesta richiede l'autenticazione:

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();

Una volta completato il flusso di autenticazione, l'attività deve chiamare il metodo setResult(), passando un valore RESULT_OK e impostare l'ulteriore EXTRA_AUTHENTICATION_RESULT sull'oggetto FillResponse che include il set di dati completato. Il seguente codice mostra un esempio di come restituire il risultato una volta completati i flussi di autenticazione:

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

Nel caso in cui sia necessario sbloccare un set di dati della carta di credito, il servizio può mostrare un'interfaccia utente in cui viene richiesto il CVC. Puoi nascondere i dati finché il set di dati non viene sbloccato presentando dati boilerplate, come il nome della banca e le ultime quattro cifre del numero della carta di credito. L'esempio seguente mostra come richiedere l'autenticazione per un set di dati e nascondere i dati finché l'utente non fornisce il 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();

Una volta che l'attività convalida il CVC, deve chiamare il metodo setResult(), passando un valore RESULT_OK e impostare l'importo aggiuntivo EXTRA_AUTHENTICATION_RESULT su un oggetto Dataset contenente il numero della carta di credito e la data di scadenza. Il nuovo set di dati sostituisce il set di dati che richiede l'autenticazione e le visualizzazioni vengono compilate immediatamente. Il seguente codice mostra un esempio di come restituire il set di dati una volta che l'utente ha fornito il 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);

Organizzare i dati in gruppi logici

I servizi di compilazione automatica devono organizzare i dati in gruppi logici che isolano i concetti da domini diversi. In questa pagina, questi gruppi logici sono definiti partizioni. Il seguente elenco mostra esempi tipici di partizioni e campi:

  • Credenziali, inclusi i campi di nome utente e password.
  • Indirizzo, che include i campi via, città, stato e codice postale.
  • Dati di pagamento, inclusi i campi del numero della carta di credito, della data di scadenza e del codice di verifica.

Un servizio di compilazione automatica che esegue correttamente il partizionamento dei dati è in grado di proteggere meglio i dati dei propri utenti perché non espone i dati da più di una partizione di un set di dati. Ad esempio, un set di dati che include credenziali non deve includere i dati di pagamento. L'organizzazione dei dati in partizioni consente al servizio di esporre la quantità minima di informazioni pertinenti necessarie per soddisfare una richiesta.

L'organizzazione dei dati in partizioni consente ai servizi di completare le attività che hanno visualizzazioni da più partizioni inviando al contempo la quantità minima di dati pertinenti all'app client. Ad esempio, prendi in considerazione un'attività che include viste per nome utente, password, via e città e un servizio di compilazione automatica con i seguenti dati:

Partizione Campo 1 Campo 2
Credenziali nomeutente_lavoro password_lavoro
nome_utente_personale password_personale
Indirizzo via_lavoro città_lavoro
strada_personale città_personale

Il servizio può preparare un set di dati che includa la partizione delle credenziali per gli account di lavoro e personali. Quando l'utente sceglie un set di dati, una risposta di compilazione automatica successiva può fornire l'indirizzo di lavoro o personale, a seconda della prima scelta dell'utente.

Un servizio può identificare il campo che ha originato la richiesta chiamando il metodo isFocused() mentre si attraversa l'oggetto AssistStructure. In questo modo il servizio può preparare un elemento FillResponse con i dati di partizione appropriati.

Compilazione automatica dei codici monouso inviati tramite SMS

Il servizio di compilazione automatica può aiutare l'utente a inserire i codici monouso inviati tramite SMS utilizzando l'API SMS Retriever.

Per utilizzare questa funzionalità, è necessario soddisfare i seguenti requisiti:

  • Il servizio di compilazione automatica è in esecuzione su Android 9 (livello API 28) o versioni successive.
  • L'utente concede il consenso al tuo servizio di compilazione automatica per leggere i codici monouso dagli SMS.
  • L'applicazione per cui stai fornendo la compilazione automatica non utilizza già l'API SMS Retriever per leggere i codici monouso.

Il tuo servizio di compilazione automatica può utilizzare SmsCodeAutofillClient, disponibile chiamando il numero SmsCodeRetriever.getAutofillClient() da Google Play Services 19.0.56 o versioni successive.

I passaggi principali per utilizzare questa API in un servizio di compilazione automatica sono:

  1. Nel servizio di compilazione automatica, utilizza hasOngoingSmsRequest da SmsCodeAutofillClient per determinare se sono presenti richieste attive per il nome del pacchetto dell'applicazione che stai compilando automaticamente. Il servizio di compilazione automatica deve mostrare un messaggio di suggerimento solo se restituisce false.
  2. Nel servizio di compilazione automatica, utilizza checkPermissionState da SmsCodeAutofillClient per verificare se il servizio sia autorizzato a compilare automaticamente i codici monouso. Questo stato di autorizzazione può essere NONE, GRANTED o DENIED. Il servizio di compilazione automatica deve mostrare una richiesta di suggerimento per gli stati NONE e GRANTED.
  3. Nell'attività di autenticazione di compilazione automatica, utilizza l'autorizzazione SmsRetriever.SEND_PERMISSION per registrare un ascolto BroadcastReceiver per SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION per ricevere il risultato del codice SMS quando è disponibile.
  4. Chiama il numero startSmsCodeRetriever al numero SmsCodeAutofillClient per iniziare ad ascoltare i codici monouso inviati tramite SMS. Se l'utente concede le autorizzazioni al servizio di compilazione automatica per recuperare i codici monouso dagli SMS, vengono cercati gli SMS ricevuti negli ultimi cinque minuti a partire da ora.

    Se il servizio di compilazione automatica deve richiedere l'autorizzazione dell'utente a leggere i codici una tantum, il valore Task restituito da startSmsCodeRetriever potrebbe non riuscire con ResolvableApiException restituito. In questo caso, devi chiamare il metodo ResolvableApiException.startResolutionForResult() per visualizzare una finestra di dialogo per il consenso per la richiesta di autorizzazione.

  5. Ricevi il risultato del codice SMS dall'intent e restituisci il codice SMS come risposta di compilazione automatica.

Scenari avanzati di compilazione automatica

Integrazione con tastiera
A partire da Android 11, la piattaforma consente alle tastiere e ad altri editor del metodo di immissione (IME) di mostrare suggerimenti di compilazione automatica in linea, anziché utilizzare un menu a discesa. Per ulteriori informazioni sul modo in cui il servizio di compilazione automatica può supportare questa funzionalità, consulta la pagina Integrare la compilazione automatica con le tastiere.
Impagina i set di dati
Una risposta di compilazione automatica di grandi dimensioni può superare le dimensioni consentite della transazione dell'oggetto Binder che rappresenta l'oggetto rimovibile necessario per elaborare la richiesta. Per evitare che il sistema Android generi un'eccezione in questi scenari, puoi ridurre FillResponse aggiungendo non più di 20 oggetti Dataset alla volta. Se la tua risposta ha bisogno di più set di dati, puoi aggiungere un set di dati che consenta agli utenti di sapere che sono disponibili più informazioni e di recuperare il gruppo di set di dati successivo quando selezionato. Per ulteriori informazioni, vedi addDataset(Dataset).
Salva i dati suddivisi su più schermi

Spesso le app dividono i dati utente su più schermate nella stessa attività, soprattutto nelle attività utilizzate per creare un nuovo account utente. Ad esempio, nella prima schermata viene richiesto un nome utente e, se il nome utente è disponibile, in un secondo schermo viene richiesta una password. In questi casi, il servizio di compilazione automatica deve attendere che l'utente inserisca entrambi i campi prima che possa essere visualizzata l'interfaccia utente di salvataggio della compilazione automatica. Per gestire questi scenari, segui questi passaggi:

  1. Nella prima richiesta di compilazione, aggiungi un pacchetto dello stato client alla risposta contenente gli ID della compilazione automatica dei campi parziali presenti nella schermata.
  2. Nella seconda richiesta di riempimento, recupera il pacchetto dello stato del client, recupera gli ID di compilazione automatica impostati nella richiesta precedente dallo stato del client e aggiungi questi ID e il flag FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE all'oggetto SaveInfo utilizzato nella seconda risposta.
  3. Nella richiesta di salvataggio, utilizza gli oggetti FillContext appropriati per ottenere il valore di ogni campo. Esiste un contesto di riempimento per richiesta di compilazione.

Per ulteriori informazioni, vedi Salvare quando i dati vengono suddivisi in più schermi.

Fornisci la logica di inizializzazione e rimozione per ogni richiesta

Ogni volta che viene inviata una richiesta di compilazione automatica, il sistema Android si associa al servizio e chiama il suo metodo onConnected(). Una volta che il servizio ha elaborato la richiesta, il sistema Android chiama il metodo onDisconnected() e svincola il servizio. Puoi implementare onConnected() per fornire il codice eseguito prima dell'elaborazione di una richiesta e onDisconnected() per fornire il codice eseguito dopo l'elaborazione della richiesta.

Personalizzazione dell'interfaccia utente per il salvataggio della compilazione automatica

I servizi di compilazione automatica possono personalizzare l'interfaccia utente per il salvataggio della compilazione automatica per aiutare gli utenti a decidere se consentire al servizio di salvare i propri dati. I servizi possono fornire informazioni aggiuntive su ciò che viene salvato tramite un semplice testo o una visualizzazione personalizzata. I servizi possono anche modificare l'aspetto del pulsante che annulla la richiesta di salvataggio e ricevere una notifica quando l'utente lo tocca. Per saperne di più, consulta la pagina di riferimento di SaveInfo.

Modalità compatibilità

La modalità di compatibilità consente ai servizi di compilazione automatica di utilizzare la struttura virtuale di accessibilità per scopi di compilazione automatica. È particolarmente utile per fornire la funzionalità di compilazione automatica nei browser che non implementano esplicitamente le API di compilazione automatica.

Per testare il servizio di compilazione automatica utilizzando la modalità di compatibilità, inserisci esplicitamente il browser o l'app che richiede questa modalità nella lista consentita. Puoi controllare quali pacchetti sono già inclusi nella lista consentita eseguendo questo comando:

$ adb shell settings get global autofill_compat_mode_allowed_packages

Se il pacchetto che stai testando non è presente nell'elenco, aggiungilo eseguendo il comando seguente, dove pkgX è il pacchetto dell'app:

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

Se l'app è un browser, utilizza resIdx per specificare l'ID risorsa del campo di immissione che contiene l'URL della pagina visualizzata.

La modalità di compatibilità presenta le seguenti limitazioni:

  • Una richiesta di salvataggio viene attivata quando il servizio utilizza il flag FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE o il metodo setTrigger(). L'opzione FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE è impostata per impostazione predefinita quando si utilizza la modalità di compatibilità.
  • Il valore di testo dei nodi potrebbe non essere disponibile nel metodo onSaveRequest(SaveRequest, SaveCallback).

Per ulteriori informazioni sulla modalità di compatibilità, incluse le limitazioni associate, consulta la documentazione di riferimento della classe AutofillService.