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