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