• 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 com.android.server.autofill;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.assist.AssistStructure;
22 import android.app.assist.AssistStructure.ViewNode;
23 import android.app.assist.AssistStructure.WindowNode;
24 import android.content.ComponentName;
25 import android.metrics.LogMaker;
26 import android.service.autofill.Dataset;
27 import android.service.autofill.InternalSanitizer;
28 import android.service.autofill.SaveInfo;
29 import android.text.TextUtils;
30 import android.util.ArrayMap;
31 import android.util.ArraySet;
32 import android.util.Slog;
33 import android.view.View;
34 import android.view.WindowManager;
35 import android.view.autofill.AutofillId;
36 import android.view.autofill.AutofillValue;
37 
38 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
39 import com.android.internal.util.ArrayUtils;
40 
41 import java.io.PrintWriter;
42 import java.util.ArrayDeque;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 
46 public final class Helper {
47 
48     private static final String TAG = "AutofillHelper";
49 
50     // TODO(b/117779333): get rid of sDebug / sVerbose and always use the service variables instead
51 
52     /**
53      * Defines a logging flag that can be dynamically changed at runtime using
54      * {@code cmd autofill set log_level debug} or through
55      * {@link android.provider.Settings.Global#AUTOFILL_LOGGING_LEVEL}.
56      */
57     public static boolean sDebug = false;
58 
59     /**
60      * Defines a logging flag that can be dynamically changed at runtime using
61      * {@code cmd autofill set log_level verbose} or through
62      * {@link android.provider.Settings.Global#AUTOFILL_LOGGING_LEVEL}.
63      */
64     public static boolean sVerbose = false;
65 
66     /**
67      * When non-null, overrides whether the UI should be shown on full-screen mode.
68      *
69      * <p>Note: access to this variable is not synchronized because it's "final" on real usage -
70      * it's only set by Shell cmd, for development purposes.
71      */
72     public static Boolean sFullScreenMode = null;
73 
Helper()74     private Helper() {
75         throw new UnsupportedOperationException("contains static members only");
76     }
77 
78     @Nullable
toArray(@ullable ArraySet<AutofillId> set)79     static AutofillId[] toArray(@Nullable ArraySet<AutofillId> set) {
80         if (set == null) return null;
81 
82         final AutofillId[] array = new AutofillId[set.size()];
83         for (int i = 0; i < set.size(); i++) {
84             array[i] = set.valueAt(i);
85         }
86         return array;
87     }
88 
89     @NonNull
paramsToString(@onNull WindowManager.LayoutParams params)90     public static String paramsToString(@NonNull WindowManager.LayoutParams params) {
91         final StringBuilder builder = new StringBuilder(25);
92         params.dumpDimensions(builder);
93         return builder.toString();
94     }
95 
96     @NonNull
getFields(@onNull Dataset dataset)97     static ArrayMap<AutofillId, AutofillValue> getFields(@NonNull Dataset dataset) {
98         final ArrayList<AutofillId> ids = dataset.getFieldIds();
99         final ArrayList<AutofillValue> values = dataset.getFieldValues();
100         final int size = ids == null ? 0 : ids.size();
101         final ArrayMap<AutofillId, AutofillValue> fields = new ArrayMap<>(size);
102         for (int i = 0; i < size; i++) {
103             fields.put(ids.get(i), values.get(i));
104         }
105         return fields;
106     }
107 
108     @NonNull
newLogMaker(int category, @NonNull String servicePackageName, int sessionId, boolean compatMode)109     private static LogMaker newLogMaker(int category, @NonNull String servicePackageName,
110             int sessionId, boolean compatMode) {
111         final LogMaker log = new LogMaker(category)
112                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName)
113                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SESSION_ID, Integer.toString(sessionId));
114         if (compatMode) {
115             log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, 1);
116         }
117         return log;
118     }
119 
120     @NonNull
newLogMaker(int category, @NonNull String packageName, @NonNull String servicePackageName, int sessionId, boolean compatMode)121     public static LogMaker newLogMaker(int category, @NonNull String packageName,
122             @NonNull String servicePackageName, int sessionId, boolean compatMode) {
123         return newLogMaker(category, servicePackageName, sessionId, compatMode)
124                 .setPackageName(packageName);
125     }
126 
127     @NonNull
newLogMaker(int category, @NonNull ComponentName componentName, @NonNull String servicePackageName, int sessionId, boolean compatMode)128     public static LogMaker newLogMaker(int category, @NonNull ComponentName componentName,
129             @NonNull String servicePackageName, int sessionId, boolean compatMode) {
130         // Remove activity name from logging
131         final ComponentName sanitizedComponentName =
132                 new ComponentName(componentName.getPackageName(), "");
133         return newLogMaker(category, servicePackageName, sessionId, compatMode)
134                 .setComponentName(sanitizedComponentName);
135     }
136 
printlnRedactedText(@onNull PrintWriter pw, @Nullable CharSequence text)137     public static void printlnRedactedText(@NonNull PrintWriter pw, @Nullable CharSequence text) {
138         if (text == null) {
139             pw.println("null");
140         } else {
141             pw.print(text.length()); pw.println("_chars");
142         }
143     }
144 
145     /**
146      * Finds the {@link ViewNode} that has the requested {@code autofillId}, if any.
147      */
148     @Nullable
findViewNodeByAutofillId(@onNull AssistStructure structure, @NonNull AutofillId autofillId)149     public static ViewNode findViewNodeByAutofillId(@NonNull AssistStructure structure,
150             @NonNull AutofillId autofillId) {
151         return findViewNode(structure, (node) -> {
152             return autofillId.equals(node.getAutofillId());
153         });
154     }
155 
findViewNode(@onNull AssistStructure structure, @NonNull ViewNodeFilter filter)156     private static ViewNode findViewNode(@NonNull AssistStructure structure,
157             @NonNull ViewNodeFilter filter) {
158         final ArrayDeque<ViewNode> nodesToProcess = new ArrayDeque<>();
159         final int numWindowNodes = structure.getWindowNodeCount();
160         for (int i = 0; i < numWindowNodes; i++) {
161             nodesToProcess.add(structure.getWindowNodeAt(i).getRootViewNode());
162         }
163         while (!nodesToProcess.isEmpty()) {
164             final ViewNode node = nodesToProcess.removeFirst();
165             if (filter.matches(node)) {
166                 return node;
167             }
168             for (int i = 0; i < node.getChildCount(); i++) {
169                 nodesToProcess.addLast(node.getChildAt(i));
170             }
171         }
172 
173         return null;
174     }
175 
176     /**
177      * Sanitize the {@code webDomain} property of the URL bar node on compat mode.
178      *
179      * @param structure Assist structure
180      * @param urlBarIds list of ids; only the first id found will be sanitized.
181      *
182      * @return the node containing the URL bar
183      */
184     @Nullable
sanitizeUrlBar(@onNull AssistStructure structure, @NonNull String[] urlBarIds)185     public static ViewNode sanitizeUrlBar(@NonNull AssistStructure structure,
186             @NonNull String[] urlBarIds) {
187         final ViewNode urlBarNode = findViewNode(structure, (node) -> {
188             return ArrayUtils.contains(urlBarIds, node.getIdEntry());
189         });
190         if (urlBarNode != null) {
191             final String domain = urlBarNode.getText().toString();
192             if (domain.isEmpty()) {
193                 if (sDebug) Slog.d(TAG, "sanitizeUrlBar(): empty on " + urlBarNode.getIdEntry());
194                 return null;
195             }
196             urlBarNode.setWebDomain(domain);
197             if (sDebug) {
198                 Slog.d(TAG, "sanitizeUrlBar(): id=" + urlBarNode.getIdEntry() + ", domain="
199                         + urlBarNode.getWebDomain());
200             }
201         }
202         return urlBarNode;
203     }
204 
205     /**
206      * Gets the value of a metric tag, or {@code 0} if not found or NaN.
207      */
getNumericValue(@onNull LogMaker log, int tag)208     static int getNumericValue(@NonNull LogMaker log, int tag) {
209         final Object value = log.getTaggedData(tag);
210         if (!(value instanceof Number)) {
211             return 0;
212         } else {
213             return ((Number) value).intValue();
214         }
215     }
216 
217     /**
218      * Gets the {@link AutofillId} of the autofillable nodes in the {@code structure}.
219      */
220     @NonNull
getAutofillIds(@onNull AssistStructure structure, boolean autofillableOnly)221     static ArrayList<AutofillId> getAutofillIds(@NonNull AssistStructure structure,
222             boolean autofillableOnly) {
223         final ArrayList<AutofillId> ids = new ArrayList<>();
224         final int size = structure.getWindowNodeCount();
225         for (int i = 0; i < size; i++) {
226             final WindowNode node = structure.getWindowNodeAt(i);
227             addAutofillableIds(node.getRootViewNode(), ids, autofillableOnly);
228         }
229         return ids;
230     }
231 
addAutofillableIds(@onNull ViewNode node, @NonNull ArrayList<AutofillId> ids, boolean autofillableOnly)232     private static void addAutofillableIds(@NonNull ViewNode node,
233             @NonNull ArrayList<AutofillId> ids, boolean autofillableOnly) {
234         if (!autofillableOnly || node.getAutofillType() != View.AUTOFILL_TYPE_NONE) {
235             ids.add(node.getAutofillId());
236         }
237         final int size = node.getChildCount();
238         for (int i = 0; i < size; i++) {
239             final ViewNode child = node.getChildAt(i);
240             addAutofillableIds(child, ids, autofillableOnly);
241         }
242     }
243 
244     @Nullable
createSanitizers(@ullable SaveInfo saveInfo)245     static ArrayMap<AutofillId, InternalSanitizer> createSanitizers(@Nullable SaveInfo saveInfo) {
246         if (saveInfo == null) return null;
247 
248         final InternalSanitizer[] sanitizerKeys = saveInfo.getSanitizerKeys();
249         if (sanitizerKeys == null) return null;
250 
251         final int size = sanitizerKeys.length;
252         final ArrayMap<AutofillId, InternalSanitizer> sanitizers = new ArrayMap<>(size);
253         if (sDebug) Slog.d(TAG, "Service provided " + size + " sanitizers");
254         final AutofillId[][] sanitizerValues = saveInfo.getSanitizerValues();
255         for (int i = 0; i < size; i++) {
256             final InternalSanitizer sanitizer = sanitizerKeys[i];
257             final AutofillId[] ids = sanitizerValues[i];
258             if (sDebug) {
259                 Slog.d(TAG, "sanitizer #" + i + " (" + sanitizer + ") for ids "
260                         + Arrays.toString(ids));
261             }
262             for (AutofillId id : ids) {
263                 sanitizers.put(id, sanitizer);
264             }
265         }
266         return sanitizers;
267     }
268 
269     /**
270      * Returns true if {@code s1} contains all characters of {@code s2}, in order.
271      */
containsCharsInOrder(String s1, String s2)272     static boolean containsCharsInOrder(String s1, String s2) {
273         int prevIndex = -1;
274         for (char ch : s2.toCharArray()) {
275             int index = TextUtils.indexOf(s1, ch, prevIndex + 1);
276             if (index == -1) {
277                 return false;
278             }
279             prevIndex = index;
280         }
281         return true;
282     }
283 
284     private interface ViewNodeFilter {
matches(ViewNode node)285         boolean matches(ViewNode node);
286     }
287 }
288