blob: 6eebac114ef8826675e950cea4b67ada33713117 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package foo.bar.fill;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.WindowNode;
import android.app.assist.AssistStructure.ViewNode;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.os.CancellationSignal;
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
import android.service.autofill.FillCallback;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.service.autofill.SaveCallback;
import android.service.autofill.SaveInfo;
import android.service.autofill.SaveRequest;
import android.util.Log;
import android.view.View;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.widget.EditText;
import android.widget.RemoteViews;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import android.widget.TextView;
import foo.bar.fill.R;
public class FillService extends AutofillService {
private static final String LOG_TAG = "FillService";
static final boolean TEST_RESPONSE_AUTH = false;
public static final String RESPONSE_ID = "RESPONSE_ID";
static final String DATASET1_NAME = "Foo";
static final String DATASET1_USERNAME = "Foo";
static final String DATASET1_PASSWORD = "1";
static final String DATASET2_NAME = "Bar";
static final String DATASET2_USERNAME = "Bar";
static final String DATASET2_PASSWORD = "12";
static final String DATASET3_NAME = "Baz";
static final String DATASET3_USERNAME = "Baz";
static final String DATASET3_PASSWORD = "123";
static final String DATASET4_NAME = "Bam";
static final String DATASET4_USERNAME = "Bam";
static final String DATASET4_PASSWORD = "1234";
static final String DATASET5_NAME = "Bak";
static final String DATASET5_USERNAME = "Bak";
static final String DATASET5_PASSWORD = "12345";
static final String EXTRA_RESPONSE_ID = "foo.bar.fill.extra.RESPONSE_ID";
static final String EXTRA_DATASET_ID = "foo.bar.fill.extra.DATASET_ID";
@Override
public void onFillRequest(@NonNull FillRequest request,
@NonNull CancellationSignal cancellationSignal,
@NonNull FillCallback callback) {
AssistStructure structure = request.getFillContexts().get(0).getStructure();
dumpNodeTree(structure);
// ViewNode username = findUsername(structure);
// ViewNode password = findPassword(structure);
ViewNode username = null;
ViewNode password = null;
final List<ViewNode> inputs = findTextInputs(structure);
if (inputs.size() > 1) {
username = inputs.get(0);
password = inputs.get(1);
}
Log.i(LOG_TAG, "found username+username:" + (username != null && password != null));
if (username != null && password != null) {
final FillResponse response;
if (TEST_RESPONSE_AUTH) {
Intent intent = new Intent(this, AuthActivity.class);
intent.putExtra(EXTRA_RESPONSE_ID, RESPONSE_ID);
IntentSender sender = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT)
.getIntentSender();
RemoteViews presentation = new RemoteViews(getPackageName(), R.layout.pathology);
// presentation.setTextViewText(R.id.text1, "First");
// Intent firstIntent = new Intent(this, FirstActivity.class);
// presentation.setOnClickPendingIntent(R.id.text1, PendingIntent.getActivity(
// this, 0, firstIntent, PendingIntent.FLAG_CANCEL_CURRENT));
// presentation.setTextViewText(R.id.text2, "Second");
// Intent secondIntent = new Intent(this, SecondActivity.class);
// presentation.setOnClickPendingIntent(R.id.text2, PendingIntent.getActivity(
// this, 0, secondIntent, PendingIntent.FLAG_CANCEL_CURRENT));
response = new FillResponse.Builder()
.setAuthentication(new AutofillId[]{username.getAutofillId(),
password.getAutofillId()}, sender, presentation)
.build();
} else {
Intent intent = new Intent(this, AuthActivity.class);
intent.putExtra(EXTRA_DATASET_ID, DATASET1_NAME);
IntentSender sender = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT)
.getIntentSender();
RemoteViews presentation1 = new RemoteViews(getPackageName(), R.layout.list_item);
presentation1.setTextViewText(R.id.text1, DATASET1_NAME);
RemoteViews presentation2 = new RemoteViews(getPackageName(), R.layout.list_item);
presentation2.setTextViewText(R.id.text1, DATASET2_NAME);
RemoteViews presentation3 = new RemoteViews(getPackageName(), R.layout.list_item);
presentation3.setTextViewText(R.id.text1, DATASET3_NAME);
RemoteViews presentation4 = new RemoteViews(getPackageName(), R.layout.list_item);
presentation4.setTextViewText(R.id.text1, DATASET4_NAME);
RemoteViews presentation5 = new RemoteViews(getPackageName(), R.layout.list_item);
presentation5.setTextViewText(R.id.text1, /*DATASET5_NAME*/ "Auth needed");
response = new FillResponse.Builder()
.addDataset(new Dataset.Builder(presentation1)
.setValue(username.getAutofillId(),
AutofillValue.forText(DATASET1_USERNAME))
.setValue(password.getAutofillId(),
AutofillValue.forText(DATASET1_PASSWORD))
.build())
// .addDataset(new Dataset.Builder(presentation2)
// .setValue(username.getAutofillId(),
// AutofillValue.forText(DATASET2_USERNAME))
// .setValue(password.getAutofillId(),
// AutofillValue.forText(DATASET2_PASSWORD))
//// .setAuthentication(sender)
// .build())
// .addDataset(new Dataset.Builder(presentation3)
// .setValue(username.getAutofillId(),
// AutofillValue.forText(DATASET3_USERNAME))
// .setValue(password.getAutofillId(),
// AutofillValue.forText(DATASET3_PASSWORD))
//// .setAuthentication(sender)
// .build())
// .addDataset(new Dataset.Builder(presentation4)
// .setValue(username.getAutofillId(),
// AutofillValue.forText(DATASET4_USERNAME))
// .setValue(password.getAutofillId(),
// AutofillValue.forText(DATASET4_PASSWORD))
//// .setAuthentication(sender)
// .build())
// .addDataset(new Dataset.Builder(presentation5)
// .setValue(username.getAutofillId(),
// AutofillValue.forText(DATASET5_USERNAME))
// .setValue(password.getAutofillId(),
// AutofillValue.forText(DATASET5_PASSWORD))
// .setAuthentication(sender)
// .build())
.setSaveInfo(new SaveInfo.Builder(
SaveInfo.SAVE_DATA_TYPE_PASSWORD
| SaveInfo.SAVE_DATA_TYPE_USERNAME,
new AutofillId[] {username.getAutofillId(),
password.getAutofillId()})
.setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
.build())
.build();
}
callback.onSuccess(response);
} else {
//callback.onFailure("Whoops");
callback.onSuccess(null);
}
}
@Override
public void onSaveRequest(@NonNull SaveRequest request, @NonNull SaveCallback callback) {
AssistStructure structure = request.getFillContexts().get(0).getStructure();
ViewNode username = findUsername(structure);
ViewNode password = findPassword(structure);
}
static void dumpNodeTree(AssistStructure structure) {
findByPredicate(structure, (node) -> {
if (node.getAutofillValue() != null) {
Log.e("class:" + LOG_TAG, node.getClassName() + " value:" + node.getAutofillValue());
}
// Log.e(LOG_TAG, (node.getAutofillValue() != null && node.getAutofillValue().isText())
// ? node.getAutofillValue().getTextValue().toString() + "-" +node.getAutofillId() : "NOPE");
return false;
});
}
List<ViewNode > findTextInputs(AssistStructure structure) {
final List<ViewNode> inputs = new ArrayList<>();
findByPredicate(structure, (node) -> {
if (node.getClassName().equals(EditText.class.getName())) {
inputs.add(node);
}
return false;
});
return inputs;
}
static ViewNode findUsername(AssistStructure structure) {
return findByPredicate(structure, (node) ->
node.getAutofillType() == View.AUTOFILL_TYPE_TEXT
&& (autofillTextValueContains(node, "username")
|| "username".equals(node.getIdEntry()))
);
}
static ViewNode findPassword(AssistStructure structure) {
return findByPredicate(structure, (node) ->
node.getAutofillType() == View.AUTOFILL_TYPE_TEXT
&& (autofillTextValueContains(node, "password")
|| "password".equals(node.getIdEntry()))
);
}
private static boolean autofillTextValueContains(ViewNode node, String text) {
return node.getAutofillValue() != null
&& node.getAutofillValue().getTextValue() != null
&& node.getAutofillValue().getTextValue().toString().toLowerCase()
.contains(text.toLowerCase());
}
private static ViewNode findByPredicate(AssistStructure structure,
Predicate<ViewNode> predicate) {
final int windowCount = structure.getWindowNodeCount();
for (int i = 0; i < windowCount; i++) {
WindowNode window = structure.getWindowNodeAt(i);
ViewNode root = window.getRootViewNode();
if (root == null) {
return null;
}
ViewNode node = findByPredicate(root, predicate);
if (node != null) {
return node;
}
}
return null;
}
private static ViewNode findByPredicate(ViewNode root, Predicate<ViewNode> predicate) {
if (root == null) {
return null;
}
if (predicate.test(root)) {
return root;
}
final int childCount = root.getChildCount();
for (int i = 0; i < childCount; i++) {
ViewNode child = root.getChildAt(i);
ViewNode node = findByPredicate(child, predicate);
if (node != null) {
return node;
}
}
return null;
}
}