androidautofillandroid-autofill-manager

Allow to disable Oreo Autofill service for a package


I am developer of a password manager app which provides an Android Autofill service (Android 8+). Some users requested that it should be possible to disable this service on a per-target-app basis. In the autofill service's onFillRequest I am adding a "dataset" with "Disable autofill for [package name]" like this:

var sender = IntentBuilder.GetDisableIntentSenderForResponse(this, query, isManual, isForDisable);

RemoteViews presentation = AutofillHelper.NewRemoteViews(PackageName,
    GetString(Resource.String.autofill_disable , new Java.Lang.Object[] { query}), Resource.Drawable.ic_menu_close_grey);

var datasetBuilder = new Dataset.Builder(presentation);
datasetBuilder.SetAuthentication(sender);

foreach (var autofillId in autofillIds)
{
    datasetBuilder.SetValue(autofillId, AutofillValue.ForText("PLACEHOLDER"));
}

responseBuilder.AddDataset(datasetBuilder.Build());

When the user clicks the "Disable dataset", an activity is launched which stores the package for which Autofill should be disabled and then immediately finishes itself.

My question: what should I return as a reply from that activity to indicate that Autofill should be invisible from now on?

I am currently doing

bool isManual = Intent.GetBooleanExtra(ChooseForAutofillActivityBase.ExtraIsManualRequest, false);

Intent reply = new Intent();
FillResponse.Builder builder = new FillResponse.Builder();
AssistStructure structure = (AssistStructure)Intent.GetParcelableExtra(AutofillManager.ExtraAssistStructure);
StructureParser parser = new StructureParser(this, structure);
try
{
    parser.ParseForFill(isManual);

}
catch (Java.Lang.SecurityException e)
{
    Log.Warn(CommonUtil.Tag, "Security exception handling request");
    SetResult(Result.Canceled);
    return;
}

AutofillFieldMetadataCollection autofillFields = parser.AutofillFields;


var autofillIds = autofillFields.GetAutofillIds();
builder.SetIgnoredIds(autofillIds);
Bundle state = new Bundle();
state.PutStringArray("AutoFillDisabledQueries", disabledValues.ToArray());

builder.SetClientState(state);
try
{
    var response = builder.Build();
    reply.PutExtra(AutofillManager.ExtraAuthenticationResult, response);
}
catch (Exception e)
{
    Kp2aLog.LogUnexpectedError(e);
    throw;
}

SetResult(Result.Ok, reply);

But

1.) The prompt for autofill does not disappear

2.) if I click disable again, the target app is force-closed (see end of message for details)

so that's obviously not the way to go... Any ideas? Thanks a lot!


Regarding point 2 above, I see the following in logcat:

12-17 09:48:31.865  Google Pixel    Error   11711   AndroidRuntime  java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:autoFillAuth:, request=16121857, result=-1, data=Intent { (has extras) }} to activity {com.sonelli.juicessh/com.sonelli.juicessh.activities.ManageConnectionActivity}: java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object java.util.List.get(int)' on a null object reference
    at android.app.ActivityThread.deliverResults(ActivityThread.java:4361)
    at android.app.ActivityThread.handleSendResult(ActivityThread.java:4403)
    at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:49)
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1809)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:193)
    at android.app.ActivityThread.main(ActivityThread.java:6680)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Solution

  • setIgnoredIds() should be used for fields that are not autofillable (for example, if the screen had an username, password, and captcha fields, you could use to ignore the latter).

    If you want to disable autofill for the activity, you need to return a Fillresponse with just the [disableAutofill()][1] set on it. Notice that disabling it doesn't persist after reboots, so you should keep track of it internally, and call that API if the activity triggers autofill again.