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