Integrate autofill with keyboards (or Input Method Editors)

Avinash Kumar
Bobble Engineering
Published in
4 min readSep 1, 2022

From Android 11, keyboards and other input-method editors (IMEs) can display autofill suggestions inline, in the form of a suggestion strip on the keyboard, instead of the system displaying these in the form of a dropdown menu. Since, these autofill suggestions can contain private data, such as passwords or credit card information, the suggestions are hidden from IME until the user selects one.

If an IME or a password manager does not support inline autofill or it is disabled from Google’s setting or other password manager autofill service, suggestions will be shown in the drop-down menu, as they were before Android 11.

Working Autofill Inline Suggestion in Mint Keyboard.

Workflow:

  1. As soon as the user focuses on an input field that triggers autofill, like password, username, or credit-card input field.
  2. The platform queries the current IME and the appropriate suggestion provider to see if they support the inline suggestion. If the current suggestion provider does not support the inline suggestion, then the suggestion will be shown in the form of a drop-down menu, as on Android 10 and lower.
  3. The platform asks the IME to provide a suggestion request. This suggestion request specifies the maximum number of suggestions the IME would like, and also provides presentation specs for each suggestion. The presentation specs specify things like maximum size, text size, color, and font data, allowing the suggestion includes a callback to inflate a View containing the suggestion’s UI.
  4. Each suggestion includes a callback to inflate a View containing the suggestion's UI.
  5. The keyboard shows the suggestions by calling the callback method to inflate each suggestion’s View. To protect the user’s confidential information, the keyboard or IME does not see what the suggestions are at this stage.
  6. If the user chooses any of the suggestions, the IME is informed the same way it would have been if the user had chosen a suggestion from a drop-down list.

Configure IMEs to support inline autofill

Your IME must set the android:supportsInlineSuggestions to true .

<input-method
xmlns:android="http://schemas.android.com/apk/res/android"
android:supportsInlineSuggestions="true"/>

When the platform needs autofill suggestions, it calls your IMEs InputMethodService.onCreateInlineSuggestionsRequest() method. You must implement the method and return a InlineSuggestionsRequest specifying the following:

  • Number of suggestions your IME would like
  • An InlinePresentationSpec for each suggestion, defining how it would be presented.

Note: If you provide fewer presentation specs than the number of suggestions requested, the last spec is used for all the excess suggestions. This means, for example, that if you provide only a single presentation spec, the suggestion provider uses that spec for all the suggestions.

When the platform has suggestions, it calls your IMEs onInlineSuggestionsResponse() method, passing a InlineSuggestionsResponse containing the suggestions. You must implement this method. Your implementation calls InlineSuggestionsResponse.getInlineSuggestions() to get the list of suggestions then inflate each suggestion by calling its InlineSuggestion.inflate() method.

Now, we are going to implement the InputMethodService.onCreateInlineSuggestionsRequest() method:

@Override
public InlineSuggestionsRequest onCreateInlineSuggestionsRequest(Bundle uiExtras) {
mSuggestionStrip.setVisibility(View.VISIBLE);
Log.d(TAG, "onCreateInlineSuggestionsRequest() called");
StylesBuilder stylesBuilder = UiVersions.newStylesBuilder();
@SuppressLint("RestrictedApi") Style style = InlineSuggestionUi.newStyleBuilder()
.setSingleIconChipStyle(
new ViewStyle.Builder()
.setBackground(
Icon.createWithResource(this, R.drawable.chip_background))
.setPadding(0, 0, 0, 0)
.build())
.setChipStyle(
new ViewStyle.Builder()
.setBackground(Icon.createWithResource(this, R.drawable.chip_background))
.setPadding(toPixel(5 + 8), 0, toPixel(5 + 8), 0)
.build())
.setStartIconStyle(new ImageViewStyle.Builder().setLayoutMargin(0, 0, 0, 0).build())
.setTitleStyle(
new TextViewStyle.Builder()
.setLayoutMargin(toPixel(4), 0, toPixel(4), 0)
.setTextColor(Color.parseColor("#FF202124"))
.setTextSize(16)
.build())
.setSubtitleStyle(
new TextViewStyle.Builder()
.setLayoutMargin(0, 0, toPixel(4), 0)
.setTextColor(Color.parseColor("#99202124")) // 60% opacity
.setTextSize(14)
.build())
.setEndIconStyle(new ImageViewStyle.Builder().setLayoutMargin(0, 0, 0, 0).build())
.build();
stylesBuilder.addStyle(style);
Bundle stylesBundle = stylesBuilder.build();

final ArrayList<InlinePresentationSpec> presentationSpecs = new ArrayList<>();
presentationSpecs.add(new InlinePresentationSpec.Builder(new Size(100, getHeight()),
new Size(740, getHeight())).setStyle(stylesBundle).build());
presentationSpecs.add(new InlinePresentationSpec.Builder(new Size(100, getHeight()),
new Size(740, getHeight())).setStyle(stylesBundle).build());

return new InlineSuggestionsRequest.Builder(presentationSpecs)
.setMaxSuggestionCount(InlineSuggestionsRequest.SUGGESTION_COUNT_UNLIMITED)
.build();
}

After implementing onCreateInlineSuggestionsRequest(), the platform will get a callback in the form of a response that contains the desired suggestion for that specific input field. Now, we have to handle the onInlineSuggestionsResponse() method:

@Override
public boolean onInlineSuggestionsResponse(InlineSuggestionsResponse response) {
Log.d(TAG, "onInlineSuggestionsResponse() called: " + response.getInlineSuggestions().size());
cancelDelayedDeletion();
postPendingResponse(response);
return true;
}

In cancelDelayedDeletion() method, we remove the callbacks from the Handler.

After that the postPendingResponse() will look like this:

private void postPendingResponse(InlineSuggestionsResponse response) {
cancelPendingResponse();
final List<InlineSuggestion> inlineSuggestions = response.getInlineSuggestions();
mResponseState = ResponseState.RECEIVE_RESPONSE;
mPendingResponse = () -> {
mPendingResponse = null;
if (mResponseState == ResponseState.START_INPUT && inlineSuggestions.isEmpty()) {
scheduleDelayedDeletion();
} else {
inflateThenShowSuggestions(inlineSuggestions);
}
mResponseState = ResponseState.RESET;
};
mHandler.post(mPendingResponse);
}

Which in-turn calls the most important method (i.e, inflateThenShowSuggestions())that inflates the inline suggestion view on the keyboard or IMEs.

private void inflateThenShowSuggestions(List<InlineSuggestion> inlineSuggestions) {
final int totalSuggestionsCount = inlineSuggestions.size();
if (inlineSuggestions.isEmpty()) {
// clear the suggestions and then return
getMainExecutor().execute(() -> updateInlineSuggestionStrip(Collections.EMPTY_LIST));
return;
}

final Map<Integer, SuggestionItem> suggestionMap = Collections.synchronizedMap((
new TreeMap<>()));
final ExecutorService executor = Executors.newSingleThreadExecutor();

for (int i = 0; i < totalSuggestionsCount; i++) {
final int index = i;
final InlineSuggestion inlineSuggestion = inlineSuggestions.get(i);
final Size size = new Size(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);

inlineSuggestion.inflate(this, size, executor, suggestionView -> {
Log.d(TAG, "new inline suggestion view ready");
if (suggestionView != null) {
suggestionView.setOnClickListener((v) -> Log.d(TAG, "Received click on the suggestion"));
suggestionView.setOnLongClickListener((v) -> {
Log.d(TAG, "Received long click on the suggestion");
return true;
});
final SuggestionItem suggestionItem = new SuggestionItem(
suggestionView, /*isAction*/ inlineSuggestion.getInfo().isPinned());
suggestionMap.put(index, suggestionItem);
} else {
suggestionMap.put(index, null);
}

// Update the UI once the last inflation completed
if (suggestionMap.size() >= totalSuggestionsCount) {
final ArrayList<SuggestionItem> suggestionItems = new ArrayList<>(
suggestionMap.values());
getMainExecutor().execute(() -> updateInlineSuggestionStrip(suggestionItems));
}
});
}
}

The full working code is available on my GitHub as well as on AOSP./

Further reading official documentation: https://developer.android.com/guide/topics/text/ime-autofill

--

--