• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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