1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package foo.bar.fill; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.PendingIntent; 22 import android.app.assist.AssistStructure; 23 import android.app.assist.AssistStructure.WindowNode; 24 import android.app.assist.AssistStructure.ViewNode; 25 import android.content.Intent; 26 import android.content.IntentSender; 27 import android.content.pm.PackageManager; 28 import android.os.CancellationSignal; 29 import android.service.autofill.AutofillService; 30 import android.service.autofill.Dataset; 31 import android.service.autofill.FillCallback; 32 import android.service.autofill.FillRequest; 33 import android.service.autofill.FillResponse; 34 import android.service.autofill.SaveCallback; 35 import android.service.autofill.SaveInfo; 36 import android.service.autofill.SaveRequest; 37 import android.util.Log; 38 import android.view.View; 39 import android.view.autofill.AutofillId; 40 import android.view.autofill.AutofillManager; 41 import android.view.autofill.AutofillValue; 42 import android.widget.EditText; 43 import android.widget.RemoteViews; 44 45 import java.util.ArrayList; 46 import java.util.List; 47 import java.util.function.Predicate; 48 49 import android.widget.TextView; 50 import foo.bar.fill.R; 51 52 public class FillService extends AutofillService { 53 private static final String LOG_TAG = "FillService"; 54 55 static final boolean TEST_RESPONSE_AUTH = false; 56 57 public static final String RESPONSE_ID = "RESPONSE_ID"; 58 59 static final String DATASET1_NAME = "Foo"; 60 static final String DATASET1_USERNAME = "Foo"; 61 static final String DATASET1_PASSWORD = "1"; 62 63 static final String DATASET2_NAME = "Bar"; 64 static final String DATASET2_USERNAME = "Bar"; 65 static final String DATASET2_PASSWORD = "12"; 66 67 static final String DATASET3_NAME = "Baz"; 68 static final String DATASET3_USERNAME = "Baz"; 69 static final String DATASET3_PASSWORD = "123"; 70 71 static final String DATASET4_NAME = "Bam"; 72 static final String DATASET4_USERNAME = "Bam"; 73 static final String DATASET4_PASSWORD = "1234"; 74 75 static final String DATASET5_NAME = "Bak"; 76 static final String DATASET5_USERNAME = "Bak"; 77 static final String DATASET5_PASSWORD = "12345"; 78 79 static final String EXTRA_RESPONSE_ID = "foo.bar.fill.extra.RESPONSE_ID"; 80 static final String EXTRA_DATASET_ID = "foo.bar.fill.extra.DATASET_ID"; 81 82 @Override onFillRequest(@onNull FillRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback)83 public void onFillRequest(@NonNull FillRequest request, 84 @NonNull CancellationSignal cancellationSignal, 85 @NonNull FillCallback callback) { 86 AssistStructure structure = request.getFillContexts().get(0).getStructure(); 87 88 dumpNodeTree(structure); 89 90 // ViewNode username = findUsername(structure); 91 // ViewNode password = findPassword(structure); 92 93 ViewNode username = null; 94 ViewNode password = null; 95 final List<ViewNode> inputs = findTextInputs(structure); 96 if (inputs.size() > 1) { 97 username = inputs.get(0); 98 password = inputs.get(1); 99 } 100 101 Log.i(LOG_TAG, "found username+username:" + (username != null && password != null)); 102 103 if (username != null && password != null) { 104 final FillResponse response; 105 106 if (TEST_RESPONSE_AUTH) { 107 Intent intent = new Intent(this, AuthActivity.class); 108 intent.putExtra(EXTRA_RESPONSE_ID, RESPONSE_ID); 109 IntentSender sender = PendingIntent.getActivity(this, 0, intent, 110 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT) 111 .getIntentSender(); 112 113 RemoteViews presentation = new RemoteViews(getPackageName(), R.layout.pathology); 114 115 // presentation.setTextViewText(R.id.text1, "First"); 116 // Intent firstIntent = new Intent(this, FirstActivity.class); 117 // presentation.setOnClickPendingIntent(R.id.text1, PendingIntent.getActivity( 118 // this, 0, firstIntent, PendingIntent.FLAG_CANCEL_CURRENT)); 119 120 // presentation.setTextViewText(R.id.text2, "Second"); 121 // Intent secondIntent = new Intent(this, SecondActivity.class); 122 // presentation.setOnClickPendingIntent(R.id.text2, PendingIntent.getActivity( 123 // this, 0, secondIntent, PendingIntent.FLAG_CANCEL_CURRENT)); 124 125 response = new FillResponse.Builder() 126 .setAuthentication(new AutofillId[]{username.getAutofillId(), 127 password.getAutofillId()}, sender, presentation) 128 .build(); 129 } else { 130 Intent intent = new Intent(this, AuthActivity.class); 131 intent.putExtra(EXTRA_DATASET_ID, DATASET1_NAME); 132 IntentSender sender = PendingIntent.getActivity(this, 0, intent, 133 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT) 134 .getIntentSender(); 135 136 RemoteViews presentation1 = new RemoteViews(getPackageName(), R.layout.list_item); 137 presentation1.setTextViewText(R.id.text1, DATASET1_NAME); 138 139 RemoteViews presentation2 = new RemoteViews(getPackageName(), R.layout.list_item); 140 presentation2.setTextViewText(R.id.text1, DATASET2_NAME); 141 142 RemoteViews presentation3 = new RemoteViews(getPackageName(), R.layout.list_item); 143 presentation3.setTextViewText(R.id.text1, DATASET3_NAME); 144 145 RemoteViews presentation4 = new RemoteViews(getPackageName(), R.layout.list_item); 146 presentation4.setTextViewText(R.id.text1, DATASET4_NAME); 147 148 RemoteViews presentation5 = new RemoteViews(getPackageName(), R.layout.list_item); 149 presentation5.setTextViewText(R.id.text1, /*DATASET5_NAME*/ "Auth needed"); 150 151 response = new FillResponse.Builder() 152 .addDataset(new Dataset.Builder(presentation1) 153 .setValue(username.getAutofillId(), 154 AutofillValue.forText(DATASET1_USERNAME)) 155 .setValue(password.getAutofillId(), 156 AutofillValue.forText(DATASET1_PASSWORD)) 157 .build()) 158 // .addDataset(new Dataset.Builder(presentation2) 159 // .setValue(username.getAutofillId(), 160 // AutofillValue.forText(DATASET2_USERNAME)) 161 // .setValue(password.getAutofillId(), 162 // AutofillValue.forText(DATASET2_PASSWORD)) 163 //// .setAuthentication(sender) 164 // .build()) 165 // .addDataset(new Dataset.Builder(presentation3) 166 // .setValue(username.getAutofillId(), 167 // AutofillValue.forText(DATASET3_USERNAME)) 168 // .setValue(password.getAutofillId(), 169 // AutofillValue.forText(DATASET3_PASSWORD)) 170 //// .setAuthentication(sender) 171 // .build()) 172 // .addDataset(new Dataset.Builder(presentation4) 173 // .setValue(username.getAutofillId(), 174 // AutofillValue.forText(DATASET4_USERNAME)) 175 // .setValue(password.getAutofillId(), 176 // AutofillValue.forText(DATASET4_PASSWORD)) 177 //// .setAuthentication(sender) 178 // .build()) 179 // .addDataset(new Dataset.Builder(presentation5) 180 // .setValue(username.getAutofillId(), 181 // AutofillValue.forText(DATASET5_USERNAME)) 182 // .setValue(password.getAutofillId(), 183 // AutofillValue.forText(DATASET5_PASSWORD)) 184 // .setAuthentication(sender) 185 // .build()) 186 .setSaveInfo(new SaveInfo.Builder( 187 SaveInfo.SAVE_DATA_TYPE_PASSWORD 188 | SaveInfo.SAVE_DATA_TYPE_USERNAME, 189 new AutofillId[] {username.getAutofillId(), 190 password.getAutofillId()}) 191 .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) 192 .build()) 193 .build(); 194 } 195 196 callback.onSuccess(response); 197 } else { 198 //callback.onFailure("Whoops"); 199 callback.onSuccess(null); 200 } 201 } 202 203 @Override onSaveRequest(@onNull SaveRequest request, @NonNull SaveCallback callback)204 public void onSaveRequest(@NonNull SaveRequest request, @NonNull SaveCallback callback) { 205 AssistStructure structure = request.getFillContexts().get(0).getStructure(); 206 ViewNode username = findUsername(structure); 207 ViewNode password = findPassword(structure); 208 } 209 dumpNodeTree(AssistStructure structure)210 static void dumpNodeTree(AssistStructure structure) { 211 findByPredicate(structure, (node) -> { 212 if (node.getAutofillValue() != null) { 213 Log.e("class:" + LOG_TAG, node.getClassName() + " value:" + node.getAutofillValue()); 214 } 215 // Log.e(LOG_TAG, (node.getAutofillValue() != null && node.getAutofillValue().isText()) 216 // ? node.getAutofillValue().getTextValue().toString() + "-" +node.getAutofillId() : "NOPE"); 217 return false; 218 }); 219 } 220 findTextInputs(AssistStructure structure)221 List<ViewNode > findTextInputs(AssistStructure structure) { 222 final List<ViewNode> inputs = new ArrayList<>(); 223 findByPredicate(structure, (node) -> { 224 if (node.getClassName().equals(EditText.class.getName())) { 225 inputs.add(node); 226 } 227 return false; 228 }); 229 return inputs; 230 } 231 findUsername(AssistStructure structure)232 static ViewNode findUsername(AssistStructure structure) { 233 return findByPredicate(structure, (node) -> 234 node.getAutofillType() == View.AUTOFILL_TYPE_TEXT 235 && (autofillTextValueContains(node, "username") 236 || "username".equals(node.getIdEntry())) 237 ); 238 } 239 findPassword(AssistStructure structure)240 static ViewNode findPassword(AssistStructure structure) { 241 return findByPredicate(structure, (node) -> 242 node.getAutofillType() == View.AUTOFILL_TYPE_TEXT 243 && (autofillTextValueContains(node, "password") 244 || "password".equals(node.getIdEntry())) 245 ); 246 } 247 autofillTextValueContains(ViewNode node, String text)248 private static boolean autofillTextValueContains(ViewNode node, String text) { 249 return node.getAutofillValue() != null 250 && node.getAutofillValue().getTextValue() != null 251 && node.getAutofillValue().getTextValue().toString().toLowerCase() 252 .contains(text.toLowerCase()); 253 } 254 findByPredicate(AssistStructure structure, Predicate<ViewNode> predicate)255 private static ViewNode findByPredicate(AssistStructure structure, 256 Predicate<ViewNode> predicate) { 257 final int windowCount = structure.getWindowNodeCount(); 258 for (int i = 0; i < windowCount; i++) { 259 WindowNode window = structure.getWindowNodeAt(i); 260 ViewNode root = window.getRootViewNode(); 261 if (root == null) { 262 return null; 263 } 264 ViewNode node = findByPredicate(root, predicate); 265 if (node != null) { 266 return node; 267 } 268 } 269 return null; 270 } 271 findByPredicate(ViewNode root, Predicate<ViewNode> predicate)272 private static ViewNode findByPredicate(ViewNode root, Predicate<ViewNode> predicate) { 273 if (root == null) { 274 return null; 275 } 276 if (predicate.test(root)) { 277 return root; 278 } 279 final int childCount = root.getChildCount(); 280 for (int i = 0; i < childCount; i++) { 281 ViewNode child = root.getChildAt(i); 282 ViewNode node = findByPredicate(child, predicate); 283 if (node != null) { 284 return node; 285 } 286 } 287 return null; 288 } 289 } 290