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