• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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