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 static com.android.server.autofill.Helper.sDebug; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.UserIdInt; 24 import android.app.ActivityManager; 25 import android.app.assist.AssistStructure; 26 import android.app.assist.AssistStructure.ViewNode; 27 import android.app.assist.AssistStructure.WindowNode; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.hardware.display.DisplayManager; 31 import android.metrics.LogMaker; 32 import android.os.UserManager; 33 import android.service.autofill.Dataset; 34 import android.service.autofill.InternalSanitizer; 35 import android.service.autofill.SaveInfo; 36 import android.text.TextUtils; 37 import android.util.ArrayMap; 38 import android.util.ArraySet; 39 import android.util.Slog; 40 import android.view.Display; 41 import android.view.View; 42 import android.view.WindowManager; 43 import android.view.autofill.AutofillId; 44 import android.view.autofill.AutofillValue; 45 import android.widget.RemoteViews; 46 47 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 48 import com.android.internal.util.ArrayUtils; 49 import com.android.server.utils.Slogf; 50 51 import java.io.PrintWriter; 52 import java.lang.ref.WeakReference; 53 import java.util.ArrayDeque; 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.concurrent.atomic.AtomicBoolean; 57 58 59 public final class Helper { 60 61 private static final String TAG = "AutofillHelper"; 62 63 // TODO(b/117779333): get rid of sDebug / sVerbose and always use the service variables instead 64 65 /** 66 * Defines a logging flag that can be dynamically changed at runtime using 67 * {@code cmd autofill set log_level debug} or through 68 * {@link android.provider.Settings.Global#AUTOFILL_LOGGING_LEVEL}. 69 */ 70 public static boolean sDebug = false; 71 72 /** 73 * Defines a logging flag that can be dynamically changed at runtime using 74 * {@code cmd autofill set log_level verbose} or through 75 * {@link android.provider.Settings.Global#AUTOFILL_LOGGING_LEVEL}. 76 */ 77 public static boolean sVerbose = false; 78 79 /** 80 * When non-null, overrides whether the UI should be shown on full-screen mode. 81 * 82 * <p>Note: access to this variable is not synchronized because it's "final" on real usage - 83 * it's only set by Shell cmd, for development purposes. 84 */ 85 public static Boolean sFullScreenMode = null; 86 Helper()87 private Helper() { 88 throw new UnsupportedOperationException("contains static members only"); 89 } 90 checkRemoteViewUriPermissions( @serIdInt int userId, @NonNull RemoteViews rView)91 private static boolean checkRemoteViewUriPermissions( 92 @UserIdInt int userId, @NonNull RemoteViews rView) { 93 final AtomicBoolean permissionsOk = new AtomicBoolean(true); 94 95 rView.visitUris(uri -> { 96 int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri); 97 boolean allowed = uriOwnerId == userId; 98 permissionsOk.set(allowed & permissionsOk.get()); 99 }); 100 101 return permissionsOk.get(); 102 } 103 104 /** 105 * Checks the URI permissions of the remote view, 106 * to see if the current userId is able to access it. 107 * 108 * Returns the RemoteView that is passed if user is able, null otherwise. 109 * 110 * TODO: instead of returning a null remoteview when 111 * the current userId cannot access an URI, 112 * return a new RemoteView with the URI removed. 113 */ sanitizeRemoteView(RemoteViews rView)114 public static @Nullable RemoteViews sanitizeRemoteView(RemoteViews rView) { 115 if (rView == null) return null; 116 117 int userId = ActivityManager.getCurrentUser(); 118 119 boolean ok = checkRemoteViewUriPermissions(userId, rView); 120 if (!ok) { 121 Slog.w(TAG, 122 "sanitizeRemoteView() user: " + userId 123 + " tried accessing resource that does not belong to them"); 124 } 125 return (ok ? rView : null); 126 } 127 128 129 @Nullable toArray(@ullable ArraySet<AutofillId> set)130 static AutofillId[] toArray(@Nullable ArraySet<AutofillId> set) { 131 if (set == null) return null; 132 133 final AutofillId[] array = new AutofillId[set.size()]; 134 for (int i = 0; i < set.size(); i++) { 135 array[i] = set.valueAt(i); 136 } 137 return array; 138 } 139 140 @NonNull paramsToString(@onNull WindowManager.LayoutParams params)141 public static String paramsToString(@NonNull WindowManager.LayoutParams params) { 142 final StringBuilder builder = new StringBuilder(25); 143 params.dumpDimensions(builder); 144 return builder.toString(); 145 } 146 147 @NonNull getFields(@onNull Dataset dataset)148 static ArrayMap<AutofillId, AutofillValue> getFields(@NonNull Dataset dataset) { 149 final ArrayList<AutofillId> ids = dataset.getFieldIds(); 150 final ArrayList<AutofillValue> values = dataset.getFieldValues(); 151 final int size = ids == null ? 0 : ids.size(); 152 final ArrayMap<AutofillId, AutofillValue> fields = new ArrayMap<>(size); 153 for (int i = 0; i < size; i++) { 154 fields.put(ids.get(i), values.get(i)); 155 } 156 return fields; 157 } 158 159 @NonNull newLogMaker(int category, @NonNull String servicePackageName, int sessionId, boolean compatMode)160 private static LogMaker newLogMaker(int category, @NonNull String servicePackageName, 161 int sessionId, boolean compatMode) { 162 final LogMaker log = new LogMaker(category) 163 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName) 164 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SESSION_ID, Integer.toString(sessionId)); 165 if (compatMode) { 166 log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, 1); 167 } 168 return log; 169 } 170 171 @NonNull newLogMaker(int category, @NonNull String packageName, @NonNull String servicePackageName, int sessionId, boolean compatMode)172 public static LogMaker newLogMaker(int category, @NonNull String packageName, 173 @NonNull String servicePackageName, int sessionId, boolean compatMode) { 174 return newLogMaker(category, servicePackageName, sessionId, compatMode) 175 .setPackageName(packageName); 176 } 177 178 @NonNull newLogMaker(int category, @NonNull ComponentName componentName, @NonNull String servicePackageName, int sessionId, boolean compatMode)179 public static LogMaker newLogMaker(int category, @NonNull ComponentName componentName, 180 @NonNull String servicePackageName, int sessionId, boolean compatMode) { 181 // Remove activity name from logging 182 final ComponentName sanitizedComponentName = 183 new ComponentName(componentName.getPackageName(), ""); 184 return newLogMaker(category, servicePackageName, sessionId, compatMode) 185 .setComponentName(sanitizedComponentName); 186 } 187 printlnRedactedText(@onNull PrintWriter pw, @Nullable CharSequence text)188 public static void printlnRedactedText(@NonNull PrintWriter pw, @Nullable CharSequence text) { 189 if (text == null) { 190 pw.println("null"); 191 } else { 192 pw.print(text.length()); pw.println("_chars"); 193 } 194 } 195 196 /** 197 * Finds the {@link ViewNode} that has the requested {@code autofillId}, if any. 198 */ 199 @Nullable findViewNodeByAutofillId(@onNull AssistStructure structure, @NonNull AutofillId autofillId)200 public static ViewNode findViewNodeByAutofillId(@NonNull AssistStructure structure, 201 @NonNull AutofillId autofillId) { 202 return findViewNode(structure, (node) -> { 203 return autofillId.equals(node.getAutofillId()); 204 }); 205 } 206 findViewNode(@onNull AssistStructure structure, @NonNull ViewNodeFilter filter)207 private static ViewNode findViewNode(@NonNull AssistStructure structure, 208 @NonNull ViewNodeFilter filter) { 209 final ArrayDeque<ViewNode> nodesToProcess = new ArrayDeque<>(); 210 final int numWindowNodes = structure.getWindowNodeCount(); 211 for (int i = 0; i < numWindowNodes; i++) { 212 nodesToProcess.add(structure.getWindowNodeAt(i).getRootViewNode()); 213 } 214 while (!nodesToProcess.isEmpty()) { 215 final ViewNode node = nodesToProcess.removeFirst(); 216 if (filter.matches(node)) { 217 return node; 218 } 219 for (int i = 0; i < node.getChildCount(); i++) { 220 nodesToProcess.addLast(node.getChildAt(i)); 221 } 222 } 223 224 return null; 225 } 226 227 /** 228 * Sanitize the {@code webDomain} property of the URL bar node on compat mode. 229 * 230 * @param structure Assist structure 231 * @param urlBarIds list of ids; only the first id found will be sanitized. 232 * 233 * @return the node containing the URL bar 234 */ 235 @Nullable sanitizeUrlBar(@onNull AssistStructure structure, @NonNull String[] urlBarIds)236 public static ViewNode sanitizeUrlBar(@NonNull AssistStructure structure, 237 @NonNull String[] urlBarIds) { 238 final ViewNode urlBarNode = findViewNode(structure, (node) -> { 239 return ArrayUtils.contains(urlBarIds, node.getIdEntry()); 240 }); 241 if (urlBarNode != null) { 242 final String domain = urlBarNode.getText().toString(); 243 if (domain.isEmpty()) { 244 if (sDebug) Slog.d(TAG, "sanitizeUrlBar(): empty on " + urlBarNode.getIdEntry()); 245 return null; 246 } 247 urlBarNode.setWebDomain(domain); 248 if (sDebug) { 249 Slog.d(TAG, "sanitizeUrlBar(): id=" + urlBarNode.getIdEntry() + ", domain=" 250 + urlBarNode.getWebDomain()); 251 } 252 } 253 return urlBarNode; 254 } 255 256 /** 257 * Gets the value of a metric tag, or {@code 0} if not found or NaN. 258 */ getNumericValue(@onNull LogMaker log, int tag)259 static int getNumericValue(@NonNull LogMaker log, int tag) { 260 final Object value = log.getTaggedData(tag); 261 if (!(value instanceof Number)) { 262 return 0; 263 } else { 264 return ((Number) value).intValue(); 265 } 266 } 267 268 /** 269 * Gets the {@link AutofillId} of the autofillable nodes in the {@code structure}. 270 */ 271 @NonNull getAutofillIds(@onNull AssistStructure structure, boolean autofillableOnly)272 static ArrayList<AutofillId> getAutofillIds(@NonNull AssistStructure structure, 273 boolean autofillableOnly) { 274 final ArrayList<AutofillId> ids = new ArrayList<>(); 275 final int size = structure.getWindowNodeCount(); 276 for (int i = 0; i < size; i++) { 277 final WindowNode node = structure.getWindowNodeAt(i); 278 addAutofillableIds(node.getRootViewNode(), ids, autofillableOnly); 279 } 280 return ids; 281 } 282 addAutofillableIds(@onNull ViewNode node, @NonNull ArrayList<AutofillId> ids, boolean autofillableOnly)283 private static void addAutofillableIds(@NonNull ViewNode node, 284 @NonNull ArrayList<AutofillId> ids, boolean autofillableOnly) { 285 if (!autofillableOnly || node.getAutofillType() != View.AUTOFILL_TYPE_NONE) { 286 ids.add(node.getAutofillId()); 287 } 288 final int size = node.getChildCount(); 289 for (int i = 0; i < size; i++) { 290 final ViewNode child = node.getChildAt(i); 291 addAutofillableIds(child, ids, autofillableOnly); 292 } 293 } 294 295 @Nullable createSanitizers(@ullable SaveInfo saveInfo)296 static ArrayMap<AutofillId, InternalSanitizer> createSanitizers(@Nullable SaveInfo saveInfo) { 297 if (saveInfo == null) return null; 298 299 final InternalSanitizer[] sanitizerKeys = saveInfo.getSanitizerKeys(); 300 if (sanitizerKeys == null) return null; 301 302 final int size = sanitizerKeys.length; 303 final ArrayMap<AutofillId, InternalSanitizer> sanitizers = new ArrayMap<>(size); 304 if (sDebug) Slog.d(TAG, "Service provided " + size + " sanitizers"); 305 final AutofillId[][] sanitizerValues = saveInfo.getSanitizerValues(); 306 for (int i = 0; i < size; i++) { 307 final InternalSanitizer sanitizer = sanitizerKeys[i]; 308 final AutofillId[] ids = sanitizerValues[i]; 309 if (sDebug) { 310 Slog.d(TAG, "sanitizer #" + i + " (" + sanitizer + ") for ids " 311 + Arrays.toString(ids)); 312 } 313 for (AutofillId id : ids) { 314 sanitizers.put(id, sanitizer); 315 } 316 } 317 return sanitizers; 318 } 319 320 /** 321 * Returns true if {@code s1} contains all characters of {@code s2}, in order. 322 */ containsCharsInOrder(String s1, String s2)323 static boolean containsCharsInOrder(String s1, String s2) { 324 int prevIndex = -1; 325 for (char ch : s2.toCharArray()) { 326 int index = TextUtils.indexOf(s1, ch, prevIndex + 1); 327 if (index == -1) { 328 return false; 329 } 330 prevIndex = index; 331 } 332 return true; 333 } 334 335 /** 336 * Gets a context with the proper display id. 337 * 338 * <p>For most cases it will return the provided context, but on devices that 339 * {@link UserManager#isVisibleBackgroundUsersEnabled() support visible background users}, it 340 * will return a context with the display pased as parameter. 341 */ getDisplayContext(Context context, int displayId)342 static Context getDisplayContext(Context context, int displayId) { 343 if (!UserManager.isVisibleBackgroundUsersEnabled()) { 344 return context; 345 } 346 if (context.getDisplayId() == displayId) { 347 if (sDebug) { 348 Slogf.d(TAG, "getDisplayContext(): context %s already has displayId %d", context, 349 displayId); 350 } 351 return context; 352 } 353 if (sDebug) { 354 Slogf.d(TAG, "Creating context for display %d", displayId); 355 } 356 Display display = context.getSystemService(DisplayManager.class).getDisplay(displayId); 357 if (display == null) { 358 Slogf.wtf(TAG, "Could not get context with displayId %d, Autofill operations will " 359 + "probably fail)", displayId); 360 return context; 361 } 362 363 return context.createDisplayContext(display); 364 } 365 weakDeref(WeakReference<T> weakRef, String tag, String prefix)366 static <T> @Nullable T weakDeref(WeakReference<T> weakRef, String tag, String prefix) { 367 T deref = weakRef.get(); 368 if (deref == null) { 369 Slog.wtf(tag, prefix + "fail to deref " + weakRef); 370 } 371 return deref; 372 } 373 374 private interface ViewNodeFilter { matches(ViewNode node)375 boolean matches(ViewNode node); 376 } 377 } 378