Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Oreo: what should I do to publish my app as an Autofill service provider?

I'm an independent developer of a password manager app. What should I do, or what should I implement (interface/API/Service), to make my app an Autofill service provider (in a device with Android Oreo API >= 26)?

I have read all kinds of related documentation, but I can't understand how to do this. Am I missing something?

At the moment I see that only well-known password managers support this feature:

Image of apps that provide Autofill service

Any hints are welcome.

like image 482
Barmaley Avatar asked Nov 18 '17 15:11

Barmaley


1 Answers

As usual, Google's own examples repository provides a good starting point for learning the Autofill Framework's API, and covers much more material than I can fit into an answer. Here's an overview of the key concepts. From the description in the documentation, we want to create an Autofill service that will handle requests from other apps (the clients) to store and retrieve Autofill field data.

First, we need to create a service provider class that fulfills this contract. We can extend the base AutofillService class:

import android.service.autofill.AutofillService;
...
public class MyAutofillService extends AutofillService {
    ...
    @Override
    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
        FillCallback callback) { ... }

    @Override
    public void onSaveRequest(SaveRequest request, SaveCallback callback) { ... }
}

The service's onFillRequest() and onSaveRequest() methods are the most significant for our understanding. The Android system calls onFillRequest() to determine if our service can autofill fields for a particular activity, and gives the method a FillRequest which contains the context and view information that our service will examine for fillable fields. When the service finishes, it invokes the provided callback with the appropriate autofill data.

Here's a dramatically simplified overview of the basic steps needed to provide autofill suggestions for a FillRequest:

@Override
public void onFillRequest(FillRequest request, CancellationSignal signal, FillCallback callback) {
    List<FillContext> contexts = request.getFillContexts();
    AssistStructure structure = contexts.get(contexts.size() - 1);
    WindowNode windowNode = structure.getWindowNodeAt(0);
    ViewNode viewNode = windowNode.getRootViewNode(); // pretend this is an EditText

    String suggestionText = "This will appear in the autofill list for 'viewNode'.";
    RemoteViews suggestion = new RemoteViews(getPackageName(), R.layout.autofill_suggestion);
    suggestion.setTextViewText(R.id.autofill_suggestion, suggestionText);

    Dataset suggestionDataset = new Dataset.Builder(suggestion) 
        .setValue(viewNode.getAutoFillId(), AutofillValue.forText(suggestionText))
        .build();

    FillResponse response = new FillResponse.Builder() 
        .addDataset(suggestionDataset)
        .build();

    callback.onSuccess(response);
}

As we can see, the Autofill API requires a lot of code just to provide a single, static autofill suggestion for a View that we already know in advance—the example assumes that viewNode is a text input field that we want to provide autofill suggestions for. In reality, this example is too simple, but I wanted to clearly show the minimum implementation. For each WindowNode, we need to walk through the view tree of the root ViewNode and each of its children to find each of the input fields that our service wishes to provide autofill data for, and then create a RemoteViews and Dataset that contains the autofill suggestion for each field that we'll add to the FillResponse using FillResponse.Builder.addDataset(). This example doesn't show the plain XML layout for the R.layout.autofill_suggestion TextView used to create the suggestion display item for a RemoteViews.

Similarly, Android calls onSaveRequest() when a user wants to save the data in an activity's fields for future completion requests and injects a SaveRequest that our service uses to find autofill data to remember.

The specific implementation of each of these methods will depend on the type of Autofill data that our app provides. Autofill services must conscientiously examine the characteristics of each field and carefully select a set of appropriate autofill suggestions to avoid leaking the user's data to a malicious client activity (see comments). For a password manager in particular, we need to pay special attention to properly authenticating a user of the service and providing a safe set of suggestions when requesting and saving autofill data.

We can now register the service in the <application> block of the project's AndroidManifest.xml:

<service
    android:name=".MyAutofillService"
    android:label="Multi-Dataset Autofill Service"
    android:permission="android.permission.BIND_AUTOFILL_SERVICE">
    <meta-data
        android:name="android.autofill"
        android:resource="@xml/multidataset_service" />

    <intent-filter>
        <action android:name="android.service.autofill.AutofillService" />
    </intent-filter>
</service>

As shown, this binds our Autofill service as an available option that appears in the Autofill services list show in the question. The android:name attribute must match the name of our AutofillService class, and our app must declare the BIND_AUTOFILL_SERVICE permission. Change the value of android:label to a suitable name for the service (for example, "Autofill with Password Manager"). Alternatively, set this in a string resource. Note also that we should provide a "settings" activity used to configure our service which we specify in the <meta‑data> for android.autofill:

<autofill-service android:settingsActivity="foo.bar.SettingsActivity" />

Then, the user can enable our Autofill service from their device Settings. We can broadcast the ACTION_REQUEST_SET_AUTOFILL_SERVICE intent during setup or first launch to assist the user in finding this screen.

like image 172
Cy Rossignol Avatar answered Sep 27 '22 19:09

Cy Rossignol