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.os.CancellationSignal; 28 import android.service.autofill.AutofillService; 29 import android.service.autofill.Dataset; 30 import android.service.autofill.FillCallback; 31 import android.service.autofill.FillRequest; 32 import android.service.autofill.FillResponse; 33 import android.service.autofill.SaveCallback; 34 import android.service.autofill.SaveInfo; 35 import android.service.autofill.SaveRequest; 36 import android.view.View; 37 import android.view.autofill.AutofillId; 38 import android.view.autofill.AutofillValue; 39 import android.widget.RemoteViews; 40 41 import java.util.function.Predicate; 42 43 import foo.bar.fill.R; 44 45 public class FillService extends AutofillService { 46 static final boolean TEST_RESPONSE_AUTH = false; 47 48 public static final String RESPONSE_ID = "RESPONSE_ID"; 49 50 static final String DATASET1_NAME = "Foo"; 51 static final String DATASET1_USERNAME = "Foo"; 52 static final String DATASET1_PASSWORD = "1"; 53 54 static final String DATASET2_NAME = "Bar"; 55 static final String DATASET2_USERNAME = "Bar"; 56 static final String DATASET2_PASSWORD = "12"; 57 58 static final String DATASET3_NAME = "Baz"; 59 static final String DATASET3_USERNAME = "Baz"; 60 static final String DATASET3_PASSWORD = "123"; 61 62 static final String DATASET4_NAME = "Bam"; 63 static final String DATASET4_USERNAME = "Bam"; 64 static final String DATASET4_PASSWORD = "1234"; 65 66 static final String DATASET5_NAME = "Bak"; 67 static final String DATASET5_USERNAME = "Bak"; 68 static final String DATASET5_PASSWORD = "12345"; 69 70 static final String EXTRA_RESPONSE_ID = "foo.bar.fill.extra.RESPONSE_ID"; 71 static final String EXTRA_DATASET_ID = "foo.bar.fill.extra.DATASET_ID"; 72 73 @Override onFillRequest(@onNull FillRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback)74 public void onFillRequest(@NonNull FillRequest request, 75 @NonNull CancellationSignal cancellationSignal, 76 @NonNull FillCallback callback) { 77 AssistStructure structure = request.getFillContexts().get(0).getStructure(); 78 79 ViewNode username = findUsername(structure); 80 ViewNode password = findPassword(structure); 81 82 if (username != null && password != null) { 83 final FillResponse response; 84 85 if (TEST_RESPONSE_AUTH) { 86 Intent intent = new Intent(this, AuthActivity.class); 87 intent.putExtra(EXTRA_RESPONSE_ID, RESPONSE_ID); 88 IntentSender sender = PendingIntent.getActivity(this, 0, intent, 89 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT) 90 .getIntentSender(); 91 92 RemoteViews presentation = new RemoteViews(getPackageName(), R.layout.pathology); 93 94 // presentation.setTextViewText(R.id.text1, "First"); 95 // Intent firstIntent = new Intent(this, FirstActivity.class); 96 // presentation.setOnClickPendingIntent(R.id.text1, PendingIntent.getActivity( 97 // this, 0, firstIntent, PendingIntent.FLAG_CANCEL_CURRENT)); 98 99 // presentation.setTextViewText(R.id.text2, "Second"); 100 // Intent secondIntent = new Intent(this, SecondActivity.class); 101 // presentation.setOnClickPendingIntent(R.id.text2, PendingIntent.getActivity( 102 // this, 0, secondIntent, PendingIntent.FLAG_CANCEL_CURRENT)); 103 104 response = new FillResponse.Builder() 105 .setAuthentication(new AutofillId[]{username.getAutofillId(), 106 password.getAutofillId()}, sender, presentation) 107 .build(); 108 } else { 109 Intent intent = new Intent(this, AuthActivity.class); 110 intent.putExtra(EXTRA_DATASET_ID, DATASET1_NAME); 111 IntentSender sender = PendingIntent.getActivity(this, 0, intent, 112 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT) 113 .getIntentSender(); 114 115 RemoteViews presentation1 = new RemoteViews(getPackageName(), R.layout.list_item); 116 presentation1.setTextViewText(R.id.text1, DATASET1_NAME); 117 118 RemoteViews presentation2 = new RemoteViews(getPackageName(), R.layout.list_item); 119 presentation2.setTextViewText(R.id.text1, DATASET2_NAME); 120 121 RemoteViews presentation3 = new RemoteViews(getPackageName(), R.layout.list_item); 122 presentation3.setTextViewText(R.id.text1, DATASET3_NAME); 123 124 RemoteViews presentation4 = new RemoteViews(getPackageName(), R.layout.list_item); 125 presentation4.setTextViewText(R.id.text1, DATASET4_NAME); 126 127 RemoteViews presentation5 = new RemoteViews(getPackageName(), R.layout.list_item); 128 presentation5.setTextViewText(R.id.text1, /*DATASET5_NAME*/ "Auth needed"); 129 130 response = new FillResponse.Builder() 131 .addDataset(new Dataset.Builder(presentation1) 132 .setValue(username.getAutofillId(), 133 AutofillValue.forText(DATASET1_USERNAME)) 134 .setValue(password.getAutofillId(), 135 AutofillValue.forText(DATASET1_PASSWORD)) 136 .build()) 137 .addDataset(new Dataset.Builder(presentation2) 138 .setValue(username.getAutofillId(), 139 AutofillValue.forText(DATASET2_USERNAME)) 140 .setValue(password.getAutofillId(), 141 AutofillValue.forText(DATASET2_PASSWORD)) 142 // .setAuthentication(sender) 143 .build()) 144 .addDataset(new Dataset.Builder(presentation3) 145 .setValue(username.getAutofillId(), 146 AutofillValue.forText(DATASET3_USERNAME)) 147 .setValue(password.getAutofillId(), 148 AutofillValue.forText(DATASET3_PASSWORD)) 149 // .setAuthentication(sender) 150 .build()) 151 .addDataset(new Dataset.Builder(presentation4) 152 .setValue(username.getAutofillId(), 153 AutofillValue.forText(DATASET4_USERNAME)) 154 .setValue(password.getAutofillId(), 155 AutofillValue.forText(DATASET4_PASSWORD)) 156 // .setAuthentication(sender) 157 .build()) 158 .addDataset(new Dataset.Builder(presentation5) 159 .setValue(username.getAutofillId(), 160 AutofillValue.forText(DATASET5_USERNAME)) 161 .setValue(password.getAutofillId(), 162 AutofillValue.forText(DATASET5_PASSWORD)) 163 .setAuthentication(sender) 164 .build()) 165 .setSaveInfo(new SaveInfo.Builder( 166 SaveInfo.SAVE_DATA_TYPE_PASSWORD 167 | SaveInfo.SAVE_DATA_TYPE_USERNAME, 168 new AutofillId[] {username.getAutofillId(), 169 password.getAutofillId()}) 170 .build()) 171 .build(); 172 } 173 174 callback.onSuccess(response); 175 } else { 176 callback.onFailure("Whoops"); 177 } 178 } 179 180 @Override onSaveRequest(@onNull SaveRequest request, @NonNull SaveCallback callback)181 public void onSaveRequest(@NonNull SaveRequest request, @NonNull SaveCallback callback) { 182 AssistStructure structure = request.getFillContexts().get(0).getStructure(); 183 ViewNode username = findUsername(structure); 184 ViewNode password = findPassword(structure); 185 } 186 findUsername(AssistStructure structure)187 static ViewNode findUsername(AssistStructure structure) { 188 return findByPredicate(structure, (node) -> 189 node.getAutofillType() == View.AUTOFILL_TYPE_TEXT 190 && "username".equals(node.getIdEntry()) 191 ); 192 } 193 findPassword(AssistStructure structure)194 static ViewNode findPassword(AssistStructure structure) { 195 return findByPredicate(structure, (node) -> 196 node.getAutofillType() == View.AUTOFILL_TYPE_TEXT 197 && "password".equals(node.getIdEntry()) 198 ); 199 } 200 findByPredicate(AssistStructure structure, Predicate<ViewNode> predicate)201 private static ViewNode findByPredicate(AssistStructure structure, 202 Predicate<ViewNode> predicate) { 203 final int windowCount = structure.getWindowNodeCount(); 204 for (int i = 0; i < windowCount; i++) { 205 WindowNode window = structure.getWindowNodeAt(i); 206 ViewNode root = window.getRootViewNode(); 207 if (root == null) { 208 return null; 209 } 210 ViewNode node = findByPredicate(root, predicate); 211 if (node != null) { 212 return node; 213 } 214 } 215 return null; 216 } 217 findByPredicate(ViewNode root, Predicate<ViewNode> predicate)218 private static ViewNode findByPredicate(ViewNode root, Predicate<ViewNode> predicate) { 219 if (root == null) { 220 return null; 221 } 222 if (predicate.test(root)) { 223 return root; 224 } 225 final int childCount = root.getChildCount(); 226 for (int i = 0; i < childCount; i++) { 227 ViewNode child = root.getChildAt(i); 228 ViewNode node = findByPredicate(child, predicate); 229 if (node != null) { 230 return node; 231 } 232 } 233 return null; 234 } 235 } 236