Un servizio di compilazione automatica è un'app che semplifica la compilazione dei moduli da parte degli utenti inserendo i dati nelle visualizzazioni di altre app. I servizi di compilazione automatica possono anche recuperare i dati utente dalle visualizzazioni di un'app e archiviarli per un utilizzo successivo. I servizi di compilazione automatica sono generalmente forniti da app che gestiscono i dati degli utenti, come i gestori delle password.
Android semplifica la compilazione dei moduli con il framework Compilazione automatica disponibile in Android 8.0 (livello API 26) e versioni successive. Gli utenti possono usufruire delle funzionalità di compilazione automatica solo se sul loro dispositivo è presente un'app che fornisce servizi di compilazione automatica.
Questa pagina mostra come implementare un servizio di compilazione automatica nella tua app. Se cerchi un esempio di codice che mostri come implementare un servizio, consulta l'esempio AutofillFramework in Java o Kotlin. Per ulteriori dettagli sul funzionamento dei servizi di compilazione automatica, consulta le pagine di riferimento per le classi 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 manifest dell'app. L'elemento <service> deve includere i seguenti attributi ed elementi:
- Attributo
android:nameche rimanda alla sottoclasse diAutofillServicenell'app che implementa il servizio. - Attributo
android:permissionche dichiara l'autorizzazioneBIND_AUTOFILL_SERVICE. - Elemento
<intent-filter>il cui elemento secondario obbligatorio<action>specifica l'azioneandroid.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 saperne di più sulle risorse XML, consulta la panoramica delle risorse dell'app.
Prompt per abilitare il 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 attivata nelle impostazioni del dispositivo. Un'app può verificare se è il servizio abilitato chiamando il metodo hasEnabledAutofillServices() della classe AutofillManager.
Se l'app non è il servizio di compilazione automatica corrente, può richiedere all'utente di
modificare le impostazioni di compilazione automatica utilizzando l'intent
ACTION_REQUEST_SET_AUTOFILL_SERVICE. L'intent restituisce un valore
di 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 visualizzazioni client quando l'utente interagisce con altre app. Se il servizio di compilazione automatica dispone di dati utente che soddisfano la richiesta, li invia nella risposta. Il sistema Android mostra una UI di compilazione automatica con i dati disponibili, come mostrato nella Figura 1:
Il framework Compilazione automatica definisce un flusso di lavoro per la compilazione delle visualizzazioni progettato per ridurre al minimo il tempo in cui il sistema Android è associato 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 può soddisfare la richiesta con i dati utente
che ha memorizzato in precedenza. Se può soddisfare la richiesta, il servizio pacchettizza i dati in oggetti Dataset. Il servizio chiama il metodo onSuccess(), passando un oggetto FillResponse che contiene gli oggetti Dataset. Se il servizio non dispone di dati per soddisfare la richiesta, passa null al metodo onSuccess(). Se si verifica un errore durante l'elaborazione della richiesta, il servizio chiama invece il metodo onFailure().
Per una spiegazione dettagliata del workflow, consulta la descrizione nella pagina di riferimento di 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, nella UI 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 navigare negli oggetti ViewNode in
AssistStructure per recuperare i dati di compilazione automatica necessari per soddisfare la richiesta.
Un servizio può recuperare i dati della compilazione automatica utilizzando i metodi della classe ViewNode, ad esempio getAutofillId().
Un servizio deve essere in grado di descrivere i contenuti di una visualizzazione 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 visualizzazione. 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 la propria euristica per descrivere i contenuti. Il servizio può utilizzare i metodi di altre classi, ad esempio getText() o getHint(), per ottenere informazioni sui contenuti della visualizzazione. Per saperne di più, consulta Fornisci 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 richiede i dati utente per compilare le visualizzazioni nelle app. Quando gli utenti compilano manualmente una visualizzazione, viene chiesto loro di salvare i dati nel servizio di compilazione automatica corrente, come mostrato nella Figura 2.
Per salvare i dati, il servizio deve indicare che è interessato ad archiviarli per un utilizzo futuro. Prima che il sistema Android invii una richiesta di salvataggio dei dati, viene inviata una richiesta di compilazione in cui il servizio ha l'opportunità di compilare le visualizzazioni. Per indicare che è interessato a salvare i dati, il servizio include un oggetto SaveInfo nella risposta alla richiesta di compilazione. L'oggetto SaveInfo contiene almeno i seguenti dati:
- Il tipo di dati utente salvati. Per un elenco dei valori
SAVE_DATAdisponibili, consultaSaveInfo. - L'insieme minimo di visualizzazioni che devono essere modificate per attivare una richiesta di salvataggio.
Ad esempio, un modulo di accesso in genere richiede all'utente di aggiornare le visualizzazioni
usernameepasswordper attivare una richiesta di salvataggio.
Un oggetto SaveInfo è associato a un oggetto FillResponse, come mostrato nel seguente esempio di codice:
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 rendere persistenti i dati utente nel metodo onSaveRequest(), che di solito 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 renderli persistenti. 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. Se non cripti le etichette, i servizi possono utilizzarle nelle visualizzazioni di presentazione se l'utente non ha eseguito l'autenticazione. In seguito, i servizi possono sostituire le etichette con i dati effettivi dopo l'autenticazione dell'utente.
Posticipa la UI 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, la UI 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 dello stesso task per inviare richieste di compilazione future e poi mostrare l'UI utilizzando una richiesta di salvataggio. Per saperne di più, consulta SaveInfo.FLAG_DELAY_SAVE.
Richiedi l'autenticazione utente
I servizi di compilazione automatica possono fornire un ulteriore livello di sicurezza richiedendo all'utente di autenticarsi prima di poter compilare le visualizzazioni. I seguenti scenari sono buoni candidati per l'implementazione dell'autenticazione utente:
- I dati utente nell'app devono essere sbloccati utilizzando una password principale o una scansione dell'impronta.
- È necessario sbloccare un set di dati specifico, ad esempio i dati della carta di credito, utilizzando un codice di sicurezza (CVC).
In uno scenario in cui il servizio richiede l'autenticazione dell'utente prima di sbloccare i dati, il servizio può presentare dati boilerplate o un'etichetta e specificare l'Intent che gestisce l'autenticazione. Se hai bisogno di dati aggiuntivi per elaborare la richiesta al termine del flusso di autenticazione, puoi aggiungerli all'intent. L'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'extra EXTRA_AUTHENTICATION_RESULT sull'oggetto FillResponse che include il set di dati compilato. Il seguente codice mostra un esempio di come restituire il risultato una volta completato il flusso 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 una UI che richiede il CVC. Puoi nascondere i dati finché il set di dati non viene sbloccato presentando dati standard, 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à ha convalidato il CVC, deve chiamare il metodo setResult(), passare un valore RESULT_OK e impostare l'extra EXTRA_AUTHENTICATION_RESULT su un oggetto Dataset che contiene il numero carta e la data di scadenza. Il nuovo set di dati sostituisce quello 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 fornisce 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);
Organizza i dati in gruppi logici
I servizi di compilazione automatica devono organizzare i dati in gruppi logici che isolano i concetti di domini diversi. In questa pagina, questi gruppi logici sono chiamati partizioni. L'elenco seguente mostra esempi tipici di partizioni e campi:
- Credenziali, che include i campi per nome utente e password.
- Indirizzo, che include i campi relativi a via, città, stato e codice postale.
- Dati di pagamento, che includono i campi del numero carta, della data di scadenza e del codice di verifica.
Un servizio di compilazione automatica che suddivide correttamente i dati è in grado di proteggere meglio i dati dei suoi utenti non esponendo i dati di più di una partizione in un set di dati. Ad esempio, un set di dati che include credenziali non deve includere informazioni di pagamento. L'organizzazione dei dati in partizioni consente al tuo servizio di esporre la quantità minima di informazioni pertinenti necessarie per soddisfare una richiesta.
L'organizzazione dei dati in partizioni consente ai servizi di compilare attività che hanno visualizzazioni da più partizioni, inviando al contempo la quantità minima di dati pertinenti all'app client. Ad esempio, considera un'attività che include visualizzazioni per nome utente, password, via e città e un servizio di compilazione automatica che contiene i seguenti dati:
| Partition | Campo 1 | Campo 2 |
|---|---|---|
| Credenziali | work_username | work_password |
| personal_username | personal_password | |
| Indirizzo | work_street | work_city |
| personal_street | personal_city |
Il servizio può preparare un set di dati che include la partizione delle credenziali sia per l'account di lavoro sia per quello personale. 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 generato la richiesta chiamando il metodo isFocused() durante l'esplorazione dell'oggetto AssistStructure.
In questo modo, il servizio può preparare un FillResponse con i dati di partizione appropriati.
Compilazione automatica dei codici SMS monouso
Il servizio di compilazione automatica può aiutare l'utente a compilare i codici monouso inviati utilizzando l'API SMS Retriever.
Per utilizzare questa funzionalità, devono essere soddisfatti i seguenti requisiti:
- Il servizio di compilazione automatica è in esecuzione su Android 9 (livello API 28) o versioni successive.
- L'utente concede al tuo servizio di compilazione automatica l'autorizzazione a 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 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:
- Nel servizio di compilazione automatica, utilizza
hasOngoingSmsRequestdiSmsCodeAutofillClientper 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 prompt di suggerimento solo se restituiscefalse. - Nel servizio di compilazione automatica, utilizza
checkPermissionStatediSmsCodeAutofillClientper verificare se il servizio di compilazione automatica dispone dell'autorizzazione per la compilazione automatica dei codici monouso. Questo stato di autorizzazione può essereNONE,GRANTEDoDENIED. Il servizio di compilazione automatica deve mostrare un prompt di suggerimento per gli statiNONEeGRANTED. - Nell'attività di autenticazione della compilazione automatica, utilizza l'autorizzazione
SmsRetriever.SEND_PERMISSIONper registrare unBroadcastReceiverin ascolto diSmsCodeRetriever.SMS_CODE_RETRIEVED_ACTIONper ricevere il risultato del codice SMS quando è disponibile. Chiama
startSmsCodeRetrieversuSmsCodeAutofillClientper iniziare ad ascoltare i codici monouso inviati tramite SMS. Se l'utente concede le autorizzazioni al tuo servizio di compilazione automatica per recuperare i codici monouso dagli SMS, questo cerca i messaggi SMS ricevuti negli ultimi 1-5 minuti.Se il tuo servizio di compilazione automatica deve richiedere l'autorizzazione dell'utente per leggere i codici monouso, l'oggetto
Taskrestituito dastartSmsCodeRetrieverpotrebbe non riuscire con la restituzione diResolvableApiException. In questo caso, devi chiamare il metodoResolvableApiException.startResolutionForResult()per visualizzare una finestra di dialogo per il consenso per la richiesta di autorizzazione.Ricevi il risultato del codice SMS dall'intent e poi restituisci il codice SMS come risposta di compilazione automatica.
Attiva la compilazione automatica su Chrome
Chrome consente ai servizi di compilazione automatica di terze parti di compilare i moduli in modo nativo, offrendo agli utenti un'esperienza più fluida e semplice. Per utilizzare servizi di compilazione automatica di terze parti per compilare automaticamente password, passkey e altre informazioni come indirizzi e dati di pagamento, gli utenti devono selezionare Usa un altro servizio di compilazione automatica nelle impostazioni di Chrome.
Per aiutare gli utenti a usufruire della migliore esperienza di compilazione automatica possibile con il tuo servizio e Chrome su Android, i fornitori di servizi di compilazione automatica devono incoraggiare i propri utenti a specificare il fornitore di servizi preferito nelle impostazioni di Chrome.
Per aiutare gli utenti ad attivare il pulsante di attivazione/disattivazione, gli sviluppatori possono:
- Eseguire query sulle impostazioni di Chrome e scoprire se l'utente vuole utilizzare un servizio di compilazione automatica di terze parti.
- Deep link alla pagina delle impostazioni di Chrome in cui gli utenti possono attivare i servizi di compilazione automatica di terze parti.
Specifica le versioni massime di Chrome per la modalità di compatibilità
Chrome ha interrotto il supporto della modalità di compatibilità dalla versione 137 a favore della compilazione automatica Android. Mantenere la modalità di compatibilità può causare problemi di stabilità. Specifica la versione massima dei pacchetti Chrome che supportano la modalità di compatibilità per la stabilità come segue.
<autofill-service>
...
<compatibility-package android:name="com.android.chrome" android:maxLongVersionCode="711900039" />
<compatibility-package android:name="com.chrome.beta" android:maxLongVersionCode="711900039" />
<compatibility-package android:name="com.chrome.dev" android:maxLongVersionCode="711900039" />
<compatibility-package android:name="com.chrome.canary" android:maxLongVersionCode="711900039" />
...
</autofill-service>
Leggere le impostazioni di Chrome
Qualsiasi app può leggere se Chrome utilizza la modalità di compilazione automatica di terze parti che consente di utilizzare la compilazione automatica di Android. Chrome utilizza l'ContentProvider di Android per comunicare
queste informazioni. Dichiara nel file manifest di Android i canali da cui vuoi leggere le impostazioni:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY"/>
<queries>
<!-- To Query Chrome Beta: -->
<package android:name="com.chrome.beta" />
<!-- To Query Chrome Stable: -->
<package android:name="com.android.chrome" />
</queries>
Quindi, utilizza ContentResolver di Android per richiedere queste informazioni creando l'URI contenuto:
Kotlin
val CHROME_CHANNEL_PACKAGE = "com.android.chrome" // Chrome Stable.
val CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider"
val THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state"
val THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode"
val uri = Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(CHROME_CHANNEL_PACKAGE + CONTENT_PROVIDER_NAME)
.path(THIRD_PARTY_MODE_ACTIONS_URI_PATH)
.build()
val cursor = contentResolver.query(
uri,
arrayOf(THIRD_PARTY_MODE_COLUMN), // projection
null, // selection
null, // selectionArgs
null // sortOrder
)
if (cursor == null) {
// Terminate now! Chromium versions older than this don't provide this information.
}
cursor?.use { // Use the safe call operator and the use function for auto-closing
if (it.moveToFirst()) { // Check if the cursor has any rows
val index = it.getColumnIndex(THIRD_PARTY_MODE_COLUMN)
if (index != -1) { // Check if the column exists
val value = it.getInt(index)
if (0 == value) {
// 0 means that the third party mode is turned off. Chrome uses its built-in
// password manager. This is the default for new users.
} else {
// 1 means that the third party mode is turned on. Chrome forwards all
// autofill requests to Android Autofill. Users have to opt-in for this.
}
} else {
// Handle the case where the column doesn't exist. Log a warning, perhaps.
Log.w("Autofill", "Column $THIRD_PARTY_MODE_COLUMN not found in cursor")
}
}
} // The cursor is automatically closed here
Java
final String CHROME_CHANNEL_PACKAGE = "com.android.chrome"; // Chrome Stable.
final String CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider";
final String THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state";
final String THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode";
final Uri uri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(CHROME_CHANNEL_PACKAGE + CONTENT_PROVIDER_NAME)
.path(THIRD_PARTY_MODE_ACTIONS_URI_PATH)
.build();
final Cursor cursor = getContentResolver().query(
uri,
/*projection=*/new String[] {THIRD_PARTY_MODE_COLUMN},
/*selection=*/ null,
/*selectionArgs=*/ null,
/*sortOrder=*/ null);
if (cursor == null) {
// Terminate now! Chromium versions older than this don't provide this information.
}
cursor.moveToFirst(); // Retrieve the result;
int index = cursor.getColumnIndex(THIRD_PARTY_MODE_COLUMN);
if (0 == cursor.getInt(index)) {
// 0 means that the third party mode is turned off. Chrome uses its built-in
// password manager. This is the default for new users.
} else {
// 1 means that the third party mode is turned on. Chrome forwards all
// autofill requests to Android Autofill. Users have to opt-in for this.
}
Deep link alle impostazioni di Chrome
Per creare un deep link alla pagina delle impostazioni di Chrome in cui gli utenti possono attivare i servizi di compilazione automatica di terze parti, utilizza un Intent Android. Assicurati di configurare l'azione e le categorie come mostrato in questo esempio:
Kotlin
val autofillSettingsIntent = Intent(Intent.ACTION_APPLICATION_PREFERENCES)
autofillSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT)
autofillSettingsIntent.addCategory(Intent.CATEGORY_APP_BROWSER)
autofillSettingsIntent.addCategory(Intent.CATEGORY_PREFERENCE)
// Invoking the intent with a chooser allows users to select the channel they
// want to configure. If only one browser reacts to the intent, the chooser is
// skipped.
val chooser = Intent.createChooser(autofillSettingsIntent, "Pick Chrome Channel")
startActivity(chooser)
// If the caller knows which Chrome channel they want to configure,
// they can instead add a package hint to the intent, e.g.
val specificChromeIntent = Intent(Intent.ACTION_APPLICATION_PREFERENCES) // Create a *new* intent
specificChromeIntent.addCategory(Intent.CATEGORY_DEFAULT)
specificChromeIntent.addCategory(Intent.CATEGORY_APP_BROWSER)
specificChromeIntent.addCategory(Intent.CATEGORY_PREFERENCE)
specificChromeIntent.setPackage("com.android.chrome") // Set the package on the *new* intent
startActivity(specificChromeIntent) // Start the *new* intent
Java
Intent autofillSettingsIntent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES);
autofillSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
autofillSettingsIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
autofillSettingsIntent.addCategory(Intent.CATEGORY_PREFERENCE);
// Invoking the intent with a chooser allows users to select the channel they
// want to configure. If only one browser reacts to the intent, the chooser is
// skipped.
Intent chooser = Intent.createChooser(autofillSettingsIntent, "Pick Chrome Channel");
startActivity(chooser);
// If the caller knows which Chrome channel they want to configure,
// they can instead add a package hint to the intent, e.g.
autofillSettingsIntent.setPackage("com.android.chrome");
startActivity(autofillSettingsIntent);
Scenari di compilazione automatica avanzata
Utilizza la compilazione automatica nei seguenti scenari:
Integra con la tastiera
A partire da Android 11, la piattaforma consente alle tastiere e ad altri editor di metodi di immissione (IME) di visualizzare i suggerimenti di compilazione automatica in linea, anziché utilizzare un menu a discesa. Per saperne di più su come il tuo servizio di compilazione automatica può supportare questa funzionalità, consulta Integra la compilazione automatica con le tastiere.
Impagina i set di dati
Una risposta di compilazione automatica di grandi dimensioni può superare le dimensioni della transazione consentite dell'oggetto Binder che rappresenta l'oggetto remoto necessario per elaborare la richiesta. Per evitare che il sistema Android generi un'eccezione in questi scenari, puoi mantenere ridotte le dimensioni di FillResponse aggiungendo non più di 20 oggetti Dataset alla volta. Se la risposta richiede più set di dati, puoi aggiungere un set di dati che informa gli utenti che sono disponibili ulteriori informazioni e recupera il gruppo di set di dati successivo quando viene selezionato. Per saperne di più, consulta addDataset(Dataset).
Salva i dati suddivisi in più schermate
Le app spesso suddividono i dati utente su più schermate all'interno della stessa attività, ad esempio per la creazione di nuovi account utente. Ad esempio, la prima schermata potrebbe richiedere un nome utente e la seconda una password. In queste situazioni, il servizio di compilazione automatica deve attendere che l'utente inserisca i dati in tutti i campi pertinenti prima di visualizzare la UI di salvataggio della compilazione automatica. Per gestire questi scenari, segui questi passaggi:
- Nella prima richiesta di compilazione, aggiungi un bundle di stato del client nella risposta che contiene gli ID di compilazione automatica dei campi parziali presenti nella schermata.
- Nella seconda richiesta di compilazione, recupera il bundle dello stato del client, ottieni 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_INVISIBLEall'oggettoSaveInfoutilizzato nella seconda risposta. Nella richiesta di salvataggio, utilizza gli oggetti
FillContextappropriati per ottenere il valore di ogni campo. Esiste un contesto di riempimento per ogni richiesta di riempimento.Per saperne di più, consulta Salva quando i dati sono suddivisi in più schermate.
Fornire la logica di inizializzazione e di chiusura per ogni richiesta
Ogni volta che viene effettuata una richiesta di compilazione automatica, il sistema Android si associa al servizio
e chiama il relativo metodo onConnected(). Una volta che il servizio ha elaborato la richiesta, il sistema Android chiama il metodo onDisconnected() e si disassocia dal servizio. Puoi implementare onConnected() per fornire il codice che viene eseguito prima dell'elaborazione di una richiesta e onDisconnected() per fornire il codice che viene eseguito dopo l'elaborazione di una richiesta.
Personalizza l'interfaccia utente di salvataggio della compilazione automatica
I servizi di compilazione automatica possono personalizzare la UI di 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 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 tocca il pulsante.
Per saperne di più, consulta la pagina di riferimento di SaveInfo.
Modalità di compatibilità
La modalità di compatibilità consente ai servizi di compilazione automatica di utilizzare la struttura virtuale di accessibilità per la 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à, aggiungi esplicitamente alla lista consentita il browser o l'app che richiede la modalità di compatibilità. 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 è elencato, aggiungilo eseguendo il seguente comando, 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_INVISIBLEo viene chiamato il metodosetTrigger().FLAG_SAVE_ON_ALL_VIEWS_INVISIBLEviene impostato per impostazione predefinita quando utilizzi 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 il riferimento alla classe AutofillService.