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