1 /* 2 * Copyright (C) 2016 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 android.service.autofill.FillEventHistory.Event.NO_SAVE_UI_REASON_NONE; 20 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE; 21 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; 22 import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE; 23 import static android.view.autofill.AutofillManager.ACTION_START_SESSION; 24 import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED; 25 import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY; 26 import static android.view.autofill.AutofillManager.NO_SESSION; 27 import static android.view.autofill.AutofillManager.RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY; 28 29 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 30 import static com.android.server.autofill.Helper.sDebug; 31 import static com.android.server.autofill.Helper.sVerbose; 32 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.app.ActivityManagerInternal; 36 import android.content.ComponentName; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.pm.PackageManager; 40 import android.content.pm.PackageManager.NameNotFoundException; 41 import android.content.pm.ResolveInfo; 42 import android.content.pm.ServiceInfo; 43 import android.graphics.Rect; 44 import android.metrics.LogMaker; 45 import android.os.AsyncTask; 46 import android.os.Binder; 47 import android.os.Bundle; 48 import android.os.Handler; 49 import android.os.IBinder; 50 import android.os.Looper; 51 import android.os.Process; 52 import android.os.RemoteCallbackList; 53 import android.os.RemoteException; 54 import android.os.SystemClock; 55 import android.os.UserHandle; 56 import android.provider.Settings; 57 import android.service.autofill.AutofillService; 58 import android.service.autofill.AutofillServiceInfo; 59 import android.service.autofill.FieldClassification; 60 import android.service.autofill.FieldClassification.Match; 61 import android.service.autofill.FillEventHistory; 62 import android.service.autofill.FillEventHistory.Event; 63 import android.service.autofill.FillEventHistory.Event.NoSaveReason; 64 import android.service.autofill.FillResponse; 65 import android.service.autofill.Flags; 66 import android.service.autofill.IAutoFillService; 67 import android.service.autofill.InlineSuggestionRenderService; 68 import android.service.autofill.SaveInfo; 69 import android.service.autofill.UserData; 70 import android.util.ArrayMap; 71 import android.util.ArraySet; 72 import android.util.DebugUtils; 73 import android.util.LocalLog; 74 import android.util.Pair; 75 import android.util.Slog; 76 import android.util.SparseArray; 77 import android.view.autofill.AutofillFeatureFlags; 78 import android.view.autofill.AutofillId; 79 import android.view.autofill.AutofillManager; 80 import android.view.autofill.AutofillManager.AutofillCommitReason; 81 import android.view.autofill.AutofillManager.SmartSuggestionMode; 82 import android.view.autofill.AutofillValue; 83 import android.view.autofill.IAutoFillManagerClient; 84 85 import com.android.internal.R; 86 import com.android.internal.annotations.GuardedBy; 87 import com.android.internal.logging.MetricsLogger; 88 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 89 import com.android.internal.os.IResultReceiver; 90 import com.android.server.LocalServices; 91 import com.android.server.autofill.AutofillManagerService.AutofillCompatState; 92 import com.android.server.autofill.AutofillManagerService.DisabledInfoCache; 93 import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks; 94 import com.android.server.autofill.ui.AutoFillUI; 95 import com.android.server.contentcapture.ContentCaptureManagerInternal; 96 import com.android.server.infra.AbstractPerUserSystemService; 97 import com.android.server.inputmethod.InputMethodManagerInternal; 98 import com.android.server.pm.UserManagerInternal; 99 import com.android.server.wm.ActivityTaskManagerInternal; 100 101 import java.io.PrintWriter; 102 import java.util.ArrayList; 103 import java.util.List; 104 import java.util.Objects; 105 import java.util.Random; 106 107 /** 108 * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the 109 * app's {@link IAutoFillService} implementation. 110 * 111 */ 112 final class AutofillManagerServiceImpl 113 extends AbstractPerUserSystemService<AutofillManagerServiceImpl, AutofillManagerService> { 114 115 private static final String TAG = "AutofillManagerServiceImpl"; 116 private static final int MAX_SESSION_ID_CREATE_TRIES = 2048; 117 118 /** Minimum interval to prune abandoned sessions */ 119 private static final int MAX_ABANDONED_SESSION_MILLIS = 30_000; 120 121 private final AutoFillUI mUi; 122 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 123 124 @GuardedBy("mLock") 125 private RemoteCallbackList<IAutoFillManagerClient> mClients; 126 127 @GuardedBy("mLock") 128 private AutofillServiceInfo mInfo; 129 130 private static final Random sRandom = new Random(); 131 132 private final LocalLog mUiLatencyHistory; 133 private final LocalLog mWtfHistory; 134 private final FieldClassificationStrategy mFieldClassificationStrategy; 135 136 @GuardedBy("mLock") 137 @Nullable 138 private RemoteInlineSuggestionRenderService mRemoteInlineSuggestionRenderService; 139 140 /** 141 * Data used for field classification. 142 */ 143 @GuardedBy("mLock") 144 private UserData mUserData; 145 146 private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true); 147 148 /** 149 * Cache of pending {@link Session}s, keyed by sessionId. 150 * 151 * <p>They're kept until the {@link AutofillService} finished handling a request, an error 152 * occurs, or the session is abandoned. 153 */ 154 @GuardedBy("mLock") 155 private final SparseArray<Session> mSessions = new SparseArray<>(); 156 157 /** 158 * Cache of FillEventHistory for active Sessions. 159 * 160 * <p>New histories are added whenever a Session is created and are kept until Sessions are 161 * removed through removeSessionLocked() 162 */ 163 @GuardedBy("mLock") 164 private final SparseArray<FillEventHistory> mFillHistories = new SparseArray<>(); 165 166 /** The last selection */ 167 @GuardedBy("mLock") 168 private FillEventHistory mEventHistory; 169 170 /** 171 * The last inline augmented autofill selection. Note that we don't log the selection from the 172 * dropdown UI since the service owns the UI in that case. 173 */ 174 @GuardedBy("mLock") 175 private FillEventHistory mAugmentedAutofillEventHistory; 176 177 /** Shared instance, doesn't need to be logged */ 178 private final AutofillCompatState mAutofillCompatState; 179 180 /** When was {@link PruneTask} last executed? */ 181 private long mLastPrune = 0; 182 183 /** 184 * Reference to the {@link RemoteFieldClassificationService}, is set on demand. 185 */ 186 @GuardedBy("mLock") 187 @Nullable 188 private RemoteFieldClassificationService mRemoteFieldClassificationService; 189 190 @GuardedBy("mLock") 191 @Nullable 192 private ServiceInfo mRemoteFieldClassificationServiceInfo; 193 194 /** 195 * Reference to the {@link RemoteAugmentedAutofillService}, is set on demand. 196 */ 197 @GuardedBy("mLock") 198 @Nullable 199 private RemoteAugmentedAutofillService mRemoteAugmentedAutofillService; 200 201 @GuardedBy("mLock") 202 @Nullable 203 private ServiceInfo mRemoteAugmentedAutofillServiceInfo; 204 205 private final InputMethodManagerInternal mInputMethodManagerInternal; 206 207 private final ContentCaptureManagerInternal mContentCaptureManagerInternal; 208 209 private final UserManagerInternal mUserManagerInternal; 210 211 private final DisabledInfoCache mDisabledInfoCache; 212 AutofillManagerServiceImpl(AutofillManagerService master, Object lock, LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui, AutofillCompatState autofillCompatState, boolean disabled, DisabledInfoCache disableCache)213 AutofillManagerServiceImpl(AutofillManagerService master, Object lock, 214 LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui, 215 AutofillCompatState autofillCompatState, 216 boolean disabled, DisabledInfoCache disableCache) { 217 super(master, lock, userId); 218 219 mUiLatencyHistory = uiLatencyHistory; 220 mWtfHistory = wtfHistory; 221 mUi = ui; 222 mFieldClassificationStrategy = new FieldClassificationStrategy(getContext(), userId); 223 mAutofillCompatState = autofillCompatState; 224 mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); 225 mContentCaptureManagerInternal = LocalServices.getService( 226 ContentCaptureManagerInternal.class); 227 mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); 228 mDisabledInfoCache = disableCache; 229 updateLocked(disabled); 230 } 231 sendActivityAssistDataToContentCapture(@onNull IBinder activityToken, @NonNull Bundle data)232 boolean sendActivityAssistDataToContentCapture(@NonNull IBinder activityToken, 233 @NonNull Bundle data) { 234 if (mContentCaptureManagerInternal != null) { 235 mContentCaptureManagerInternal.sendActivityAssistData(getUserId(), activityToken, data); 236 return true; 237 } 238 239 return false; 240 } 241 242 @GuardedBy("mLock") onBackKeyPressed()243 void onBackKeyPressed() { 244 final RemoteAugmentedAutofillService remoteService = 245 getRemoteAugmentedAutofillServiceLocked(); 246 if (remoteService != null) { 247 remoteService.onDestroyAutofillWindowsRequest(); 248 } 249 } 250 251 @GuardedBy("mLock") 252 @Override // from PerUserSystemService updateLocked(boolean disabled)253 protected boolean updateLocked(boolean disabled) { 254 forceRemoveAllSessionsLocked(); 255 final boolean enabledChanged = super.updateLocked(disabled); 256 if (enabledChanged) { 257 if (!isEnabledLocked()) { 258 final int sessionCount = mSessions.size(); 259 for (int i = sessionCount - 1; i >= 0; i--) { 260 final Session session = mSessions.valueAt(i); 261 session.removeFromServiceLocked(); 262 } 263 } 264 sendStateToClients(/* resetClient= */ false); 265 } 266 updateRemoteAugmentedAutofillService(); 267 getRemoteInlineSuggestionRenderServiceLocked(); 268 269 return enabledChanged; 270 } 271 272 @Override // from PerUserSystemService newServiceInfoLocked(@onNull ComponentName serviceComponent)273 protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) 274 throws NameNotFoundException { 275 final List<ResolveInfo> resolveInfos = 276 getContext().getPackageManager().queryIntentServicesAsUser( 277 new Intent(AutofillService.SERVICE_INTERFACE), 278 // The MATCH_INSTANT flag is added because curret autofill CTS module is 279 // defined in one apk, which makes the test autofill service installed in a 280 // instant app when the CTS tests are running in instant app mode. 281 // TODO: Remove MATCH_INSTANT flag after completing refactoring the CTS module 282 // to make the test autofill service a separate apk. 283 PackageManager.GET_META_DATA | PackageManager.MATCH_INSTANT, 284 mUserId); 285 boolean serviceHasAutofillIntentFilter = false; 286 for (ResolveInfo resolveInfo : resolveInfos) { 287 final ServiceInfo serviceInfo = resolveInfo.serviceInfo; 288 if (serviceInfo.getComponentName().equals(serviceComponent)) { 289 serviceHasAutofillIntentFilter = true; 290 break; 291 } 292 } 293 if (!serviceHasAutofillIntentFilter) { 294 Slog.w(TAG, 295 "Autofill service from '" + serviceComponent.getPackageName() + "' does" 296 + "not have intent filter " + AutofillService.SERVICE_INTERFACE); 297 throw new SecurityException("Service does not declare intent filter " 298 + AutofillService.SERVICE_INTERFACE); 299 } 300 mInfo = new AutofillServiceInfo(getContext(), serviceComponent, mUserId); 301 return mInfo.getServiceInfo(); 302 } 303 304 @Nullable getUrlBarResourceIdsForCompatMode(@onNull String packageName)305 String[] getUrlBarResourceIdsForCompatMode(@NonNull String packageName) { 306 return mAutofillCompatState.getUrlBarResourceIds(packageName, mUserId); 307 } 308 309 /** 310 * Adds the client and return the proper flags 311 * 312 * @return {@code 0} if disabled, {@code FLAG_ADD_CLIENT_ENABLED} if enabled (it might be 313 * OR'ed with {@code FLAG_AUGMENTED_AUTOFILL_REQUEST}). 314 */ addClientLocked(IAutoFillManagerClient client, ComponentName componentName, boolean credmanRequested)315 int addClientLocked(IAutoFillManagerClient client, ComponentName componentName, 316 boolean credmanRequested) { 317 synchronized (mLock) { 318 ComponentName credComponentName = getCredentialAutofillService(getContext()); 319 320 if (!credmanRequested 321 && Objects.equals(credComponentName, 322 mInfo == null ? null : mInfo.getServiceInfo().getComponentName())) { 323 // If the service component name corresponds to cred component name, then it means 324 // no autofill provider is selected by the user. Cred Autofill Service should only 325 // be active if there is a credman request. 326 return 0; 327 } 328 if (mClients == null) { 329 mClients = new RemoteCallbackList<>(); 330 } 331 mClients.register(client); 332 333 if (isEnabledLocked()) return FLAG_ADD_CLIENT_ENABLED; 334 335 // Check if it's enabled for augmented autofill 336 if (componentName != null && isAugmentedAutofillServiceAvailableLocked() 337 && isWhitelistedForAugmentedAutofillLocked(componentName)) { 338 return FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY; 339 } 340 } 341 342 // No flags / disabled 343 return 0; 344 } 345 346 @GuardedBy("mLock") removeClientLocked(IAutoFillManagerClient client)347 void removeClientLocked(IAutoFillManagerClient client) { 348 if (mClients != null) { 349 mClients.unregister(client); 350 } 351 } 352 353 @GuardedBy("mLock") setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid)354 void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) { 355 if (!isEnabledLocked()) { 356 return; 357 } 358 final Session session = mSessions.get(sessionId); 359 if (session != null && uid == session.uid) { 360 synchronized (session.mLock) { 361 session.setAuthenticationResultLocked(data, authenticationId); 362 } 363 } 364 } 365 setHasCallback(int sessionId, int uid, boolean hasIt)366 void setHasCallback(int sessionId, int uid, boolean hasIt) { 367 if (!isEnabledLocked()) { 368 return; 369 } 370 final Session session = mSessions.get(sessionId); 371 if (session != null && uid == session.uid) { 372 synchronized (mLock) { 373 session.setHasCallbackLocked(hasIt); 374 } 375 } 376 } 377 378 /** 379 * Starts a new session. 380 * 381 * @return {@code long} whose right-most 32 bits represent the session id (which is always 382 * non-negative), and the left-most contains extra flags (currently either {@code 0} or 383 * {@link AutofillManager#RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY}). 384 */ 385 @GuardedBy("mLock") startSessionLocked(@onNull IBinder activityToken, int taskId, int clientUid, @NonNull IBinder clientCallback, @NonNull AutofillId autofillId, @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, @NonNull ComponentName clientActivity, boolean compatMode, boolean bindInstantServiceAllowed, int flags)386 long startSessionLocked(@NonNull IBinder activityToken, int taskId, int clientUid, 387 @NonNull IBinder clientCallback, @NonNull AutofillId autofillId, 388 @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, 389 @NonNull ComponentName clientActivity, boolean compatMode, 390 boolean bindInstantServiceAllowed, int flags) { 391 // FLAG_AUGMENTED_AUTOFILL_REQUEST is set in the flags when standard autofill is disabled 392 // but the package is allowlisted for augmented autofill 393 boolean forAugmentedAutofillOnly = (flags 394 & FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY) != 0; 395 if (!isEnabledLocked() && !forAugmentedAutofillOnly) { 396 return 0; 397 } 398 399 // TODO(b/376482880): remove this check once autofill service supports visible 400 // background users. 401 if (mUserManagerInternal.isVisibleBackgroundFullUser(mUserId)) { 402 Slog.d(TAG, "Currently, autofill service does not support visible background users."); 403 return 0; 404 } 405 406 if (!forAugmentedAutofillOnly && isAutofillDisabledLocked(clientActivity)) { 407 // Standard autofill is enabled, but service disabled autofill for this activity; that 408 // means no session, unless the activity is allowlisted for augmented autofill 409 if (isWhitelistedForAugmentedAutofillLocked(clientActivity)) { 410 if (sDebug) { 411 Slog.d(TAG, "startSession(" + clientActivity + "): disabled by service but " 412 + "whitelisted for augmented autofill"); 413 } 414 forAugmentedAutofillOnly = true; 415 416 } else { 417 if (sDebug) { 418 Slog.d(TAG, "startSession(" + clientActivity + "): ignored because " 419 + "disabled by service and not whitelisted for augmented autofill"); 420 } 421 final IAutoFillManagerClient client = IAutoFillManagerClient.Stub 422 .asInterface(clientCallback); 423 try { 424 client.setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE, 425 /* autofillableIds= */ null); 426 } catch (RemoteException e) { 427 Slog.w(TAG, 428 "Could not notify " + clientActivity + " that it's disabled: " + e); 429 } 430 431 return NO_SESSION; 432 } 433 } 434 435 if (sVerbose) { 436 Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags 437 + ", forAugmentedAutofillOnly=" + forAugmentedAutofillOnly); 438 } 439 440 // Occasionally clean up abandoned sessions 441 pruneAbandonedSessionsLocked(); 442 443 final Session newSession = createSessionByTokenLocked(activityToken, taskId, clientUid, 444 clientCallback, hasCallback, clientActivity, compatMode, 445 bindInstantServiceAllowed, forAugmentedAutofillOnly, flags); 446 if (newSession == null) { 447 return NO_SESSION; 448 } 449 450 // Service can be null when it's only for augmented autofill 451 String servicePackageName = mInfo == null ? null : mInfo.getServiceInfo().packageName; 452 final String historyItem = 453 "id=" + newSession.id + " uid=" + clientUid + " a=" + clientActivity.toShortString() 454 + " s=" + servicePackageName 455 + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds 456 + " hc=" + hasCallback + " f=" + flags + " aa=" + forAugmentedAutofillOnly; 457 mMaster.logRequestLocked(historyItem); 458 459 synchronized (newSession.mLock) { 460 newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags); 461 } 462 463 if (forAugmentedAutofillOnly) { 464 // Must embed the flag in the response, at the high-end side of the long. 465 // (session is always positive, so we don't have to worry about the signal bit) 466 final long extraFlags = 467 ((long) RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) << 32; 468 final long result = extraFlags | newSession.id; 469 return result; 470 } else { 471 return newSession.id; 472 } 473 } 474 475 /** 476 * Remove abandoned sessions if needed. 477 */ 478 @GuardedBy("mLock") pruneAbandonedSessionsLocked()479 private void pruneAbandonedSessionsLocked() { 480 long now = System.currentTimeMillis(); 481 if (mLastPrune < now - MAX_ABANDONED_SESSION_MILLIS) { 482 mLastPrune = now; 483 484 if (mSessions.size() > 0) { 485 (new PruneTask()).execute(); 486 } 487 } 488 } 489 490 @GuardedBy("mLock") setAutofillFailureLocked( int sessionId, int uid, @NonNull List<AutofillId> ids, boolean isRefill)491 void setAutofillFailureLocked( 492 int sessionId, int uid, @NonNull List<AutofillId> ids, boolean isRefill) { 493 if (!isEnabledLocked()) { 494 Slog.wtf(TAG, "Service not enabled"); 495 return; 496 } 497 final Session session = mSessions.get(sessionId); 498 if (session == null || uid != session.uid) { 499 Slog.v(TAG, "setAutofillFailure(): no session for " + sessionId + "(" + uid + ")"); 500 return; 501 } 502 session.setAutofillFailureLocked(ids, isRefill); 503 } 504 505 @GuardedBy("mLock") setViewAutofilledLocked(int sessionId, int uid, @NonNull AutofillId id)506 void setViewAutofilledLocked(int sessionId, int uid, @NonNull AutofillId id) { 507 if (!isEnabledLocked()) { 508 Slog.wtf(TAG, "Service not enabled"); 509 return; 510 } 511 final Session session = mSessions.get(sessionId); 512 if (session == null || uid != session.uid) { 513 Slog.v(TAG, "setViewAutofilled(): no session for " + sessionId + "(" + uid + ")"); 514 return; 515 } 516 session.setViewAutofilledLocked(id); 517 } 518 519 @GuardedBy("mLock") notifyNotExpiringResponseDuringAuth(int sessionId, int uid)520 void notifyNotExpiringResponseDuringAuth(int sessionId, int uid) { 521 if (!isEnabledLocked()) { 522 Slog.wtf(TAG, "Service not enabled"); 523 return; 524 } 525 final Session session = mSessions.get(sessionId); 526 if (session == null || uid != session.uid) { 527 Slog.v(TAG, "notifyNotExpiringResponseDuringAuth(): no session for " 528 + sessionId + "(" + uid + ")"); 529 return; 530 } 531 session.setNotifyNotExpiringResponseDuringAuth(); 532 } 533 534 @GuardedBy("mLock") notifyViewEnteredIgnoredDuringAuthCount(int sessionId, int uid)535 void notifyViewEnteredIgnoredDuringAuthCount(int sessionId, int uid) { 536 if (!isEnabledLocked()) { 537 Slog.wtf(TAG, "Service not enabled"); 538 return; 539 } 540 final Session session = mSessions.get(sessionId); 541 if (session == null || uid != session.uid) { 542 Slog.v(TAG, "notifyViewEnteredIgnoredDuringAuthCount(): no session for " 543 + sessionId + "(" + uid + ")"); 544 return; 545 } 546 session.setLogViewEnteredIgnoredDuringAuth(); 547 } 548 549 @GuardedBy("mLock") setAutofillIdsAttemptedForRefill( int sessionId, @NonNull List<AutofillId> ids, int uid)550 public void setAutofillIdsAttemptedForRefill( 551 int sessionId, @NonNull List<AutofillId> ids, int uid) { 552 if (!isEnabledLocked()) { 553 Slog.wtf(TAG, "Service not enabled"); 554 return; 555 } 556 final Session session = mSessions.get(sessionId); 557 if (session == null || uid != session.uid) { 558 Slog.v(TAG, "setAutofillIdsAttemptedForRefill(): no session for " 559 + sessionId + "(" + uid + ")"); 560 return; 561 } 562 session.setAutofillIdsAttemptedForRefillLocked(ids); 563 } 564 565 @GuardedBy("mLock") finishSessionLocked(int sessionId, int uid, @AutofillCommitReason int commitReason)566 void finishSessionLocked(int sessionId, int uid, @AutofillCommitReason int commitReason) { 567 if (!isEnabledLocked()) { 568 Slog.wtf(TAG, "Service not enabled"); 569 return; 570 } 571 572 final Session session = mSessions.get(sessionId); 573 if (session == null || uid != session.uid) { 574 if (sVerbose) { 575 Slog.v(TAG, "finishSessionLocked(): no session for " + sessionId + "(" + uid + ")"); 576 } 577 return; 578 } 579 580 final Session.SaveResult saveResult = session.showSaveLocked(); 581 582 session.logContextCommittedLocked(saveResult.getNoSaveUiReason(), commitReason); 583 584 if (saveResult.isLogSaveShown()) { 585 session.logSaveUiShown(); 586 } 587 588 final boolean finished = saveResult.isRemoveSession(); 589 if (sVerbose) { 590 Slog.v(TAG, "finishSessionLocked(): session finished? " + finished 591 + ", showing save UI? " + saveResult.isLogSaveShown()); 592 } 593 594 if (finished) { 595 session.removeFromServiceLocked(); 596 } 597 } 598 599 @GuardedBy("mLock") cancelSessionLocked(int sessionId, int uid)600 void cancelSessionLocked(int sessionId, int uid) { 601 if (!isEnabledLocked()) { 602 return; 603 } 604 605 final Session session = mSessions.get(sessionId); 606 if (session == null || uid != session.uid) { 607 Slog.w(TAG, "cancelSessionLocked(): no session for " + sessionId + "(" + uid + ")"); 608 return; 609 } 610 session.removeFromServiceLocked(); 611 } 612 613 @GuardedBy("mLock") disableOwnedAutofillServicesLocked(int uid)614 void disableOwnedAutofillServicesLocked(int uid) { 615 Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo); 616 if (mInfo == null) return; 617 618 final ServiceInfo serviceInfo = mInfo.getServiceInfo(); 619 if (serviceInfo.applicationInfo.uid != uid) { 620 Slog.w(TAG, "disableOwnedServices(): ignored when called by UID " + uid 621 + " instead of " + serviceInfo.applicationInfo.uid 622 + " for service " + mInfo); 623 return; 624 } 625 626 627 final long identity = Binder.clearCallingIdentity(); 628 try { 629 final String autoFillService = getComponentNameLocked(); 630 final ComponentName componentName = serviceInfo.getComponentName(); 631 if (componentName.equals(ComponentName.unflattenFromString(autoFillService))) { 632 mMetricsLogger.action(MetricsEvent.AUTOFILL_SERVICE_DISABLED_SELF, 633 componentName.getPackageName()); 634 Settings.Secure.putStringForUser(getContext().getContentResolver(), 635 Settings.Secure.AUTOFILL_SERVICE, null, mUserId); 636 forceRemoveAllSessionsLocked(); 637 } else { 638 Slog.w(TAG, "disableOwnedServices(): ignored because current service (" 639 + serviceInfo + ") does not match Settings (" + autoFillService + ")"); 640 } 641 } finally { 642 Binder.restoreCallingIdentity(identity); 643 } 644 } 645 646 @GuardedBy("mLock") createSessionByTokenLocked(@onNull IBinder clientActivityToken, int taskId, int clientUid, @NonNull IBinder clientCallback, boolean hasCallback, @NonNull ComponentName clientActivity, boolean compatMode, boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags)647 private Session createSessionByTokenLocked(@NonNull IBinder clientActivityToken, int taskId, 648 int clientUid, @NonNull IBinder clientCallback, boolean hasCallback, 649 @NonNull ComponentName clientActivity, boolean compatMode, 650 boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags) { 651 // use random ids so that one app cannot know that another app creates sessions 652 int sessionId; 653 int tries = 0; 654 do { 655 tries++; 656 if (tries > MAX_SESSION_ID_CREATE_TRIES) { 657 Slog.w(TAG, "Cannot create session in " + MAX_SESSION_ID_CREATE_TRIES + " tries"); 658 return null; 659 } 660 661 sessionId = Math.abs(sRandom.nextInt()); 662 } while (sessionId == 0 || sessionId == NO_SESSION 663 || mSessions.indexOfKey(sessionId) >= 0); 664 665 assertCallerLocked(clientActivity, compatMode); 666 ComponentName serviceComponentName = mInfo == null ? null 667 : mInfo.getServiceInfo().getComponentName(); 668 boolean isPrimaryCredential = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0; 669 670 final Session newSession = new Session(this, mUi, getContext(), mHandler, mUserId, mLock, 671 sessionId, taskId, clientUid, clientActivityToken, clientCallback, hasCallback, 672 mUiLatencyHistory, mWtfHistory, serviceComponentName, 673 clientActivity, compatMode, bindInstantServiceAllowed, forAugmentedAutofillOnly, 674 flags, mInputMethodManagerInternal, isPrimaryCredential); 675 mSessions.put(newSession.id, newSession); 676 677 if (AutofillFeatureFlags.isMultipleFillEventHistoryEnabled() && !forAugmentedAutofillOnly) { 678 mFillHistories.put(newSession.id, new FillEventHistory(sessionId, null)); 679 } 680 681 return newSession; 682 } 683 684 /** 685 * Asserts the component is owned by the caller. 686 */ assertCallerLocked(@onNull ComponentName componentName, boolean compatMode)687 private void assertCallerLocked(@NonNull ComponentName componentName, boolean compatMode) { 688 final String packageName = componentName.getPackageName(); 689 final PackageManager pm = getContext().getPackageManager(); 690 final int callingUid = Binder.getCallingUid(); 691 final int packageUid; 692 try { 693 packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId()); 694 } catch (NameNotFoundException e) { 695 throw new SecurityException("Could not verify UID for " + componentName); 696 } 697 if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class) 698 .hasRunningActivity(callingUid, packageName)) { 699 final String[] packages = pm.getPackagesForUid(callingUid); 700 final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid; 701 Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid 702 + ") passed component (" + componentName + ") owned by UID " + packageUid); 703 704 // NOTE: not using Helper.newLogMaker() because we don't have the session id 705 final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT) 706 .setPackageName(callingPackage) 707 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, getServicePackageName()) 708 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME, 709 componentName == null ? "null" : componentName.flattenToShortString()); 710 if (compatMode) { 711 log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, 1); 712 } 713 mMetricsLogger.write(log); 714 715 throw new SecurityException("Invalid component: " + componentName); 716 } 717 } 718 719 /** 720 * Restores a session after an activity was temporarily destroyed. 721 * 722 * @param sessionId The id of the session to restore 723 * @param uid UID of the process that tries to restore the session 724 * @param activityToken The new instance of the activity 725 * @param appCallback The callbacks to the activity 726 */ restoreSession(int sessionId, int uid, @NonNull IBinder activityToken, @NonNull IBinder appCallback)727 boolean restoreSession(int sessionId, int uid, @NonNull IBinder activityToken, 728 @NonNull IBinder appCallback) { 729 final Session session = mSessions.get(sessionId); 730 731 if (session == null || uid != session.uid) { 732 return false; 733 } else { 734 session.switchActivity(activityToken, appCallback); 735 return true; 736 } 737 } 738 739 /** 740 * Updates a session and returns whether it should be restarted. 741 */ 742 @GuardedBy("mLock") updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds, AutofillValue value, int action, int flags)743 boolean updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds, 744 AutofillValue value, int action, int flags) { 745 final Session session = mSessions.get(sessionId); 746 if (session == null || session.uid != uid) { 747 if ((flags & FLAG_MANUAL_REQUEST) != 0) { 748 if (sDebug) { 749 Slog.d(TAG, "restarting session " + sessionId + " due to manual request on " 750 + autofillId); 751 } 752 return true; 753 } 754 if (sVerbose) { 755 Slog.v(TAG, "updateSessionLocked(): session gone for " + sessionId 756 + "(" + uid + ")"); 757 } 758 return false; 759 } 760 761 session.updateLocked(autofillId, virtualBounds, value, action, flags); 762 return false; 763 } 764 callOnSessionDestroyed(int sessionId)765 void callOnSessionDestroyed(int sessionId) { 766 if (sVerbose) { 767 Slog.v(TAG, "removeSessionLocked(): removed " + sessionId); 768 } 769 770 synchronized (mLock) { 771 FillEventHistory history = null; 772 773 if (AutofillFeatureFlags.isMultipleFillEventHistoryEnabled() 774 && mFillHistories != null) { 775 history = mFillHistories.get(sessionId); 776 mFillHistories.delete(sessionId); 777 } 778 779 if (mInfo == null || mInfo.getServiceInfo() == null) { 780 if (sVerbose) { 781 Slog.v(TAG, "removeSessionLocked(): early return because mInfo is null"); 782 } 783 return; 784 } 785 786 if (mMaster == null) { 787 if (sVerbose) { 788 Slog.v(TAG, "removeSessionLocked(): early return because mMaster is null"); 789 } 790 return; 791 } 792 793 RemoteFillService remoteService = 794 new RemoteFillService( 795 getContext(), 796 mInfo.getServiceInfo().getComponentName(), 797 mUserId, 798 /* callbacks= */ null, 799 mMaster.isInstantServiceAllowed(), 800 /* credentialAutofillService= */ null); 801 802 remoteService.onSessionDestroyed(history); 803 } 804 } 805 806 @GuardedBy("mLock") removeSessionLocked(int sessionId)807 void removeSessionLocked(int sessionId) { 808 mSessions.remove(sessionId); 809 if (Flags.autofillSessionDestroyed()) { 810 mHandler.sendMessage( 811 obtainMessage( 812 AutofillManagerServiceImpl::callOnSessionDestroyed, this, sessionId)); 813 } 814 } 815 816 /** 817 * Ges the previous sessions asked to be kept alive in a given activity task. 818 * 819 * @param session session calling this method (so it's excluded from the result). 820 */ 821 @Nullable 822 @GuardedBy("mLock") getPreviousSessionsLocked(@onNull Session session)823 ArrayList<Session> getPreviousSessionsLocked(@NonNull Session session) { 824 final int size = mSessions.size(); 825 ArrayList<Session> previousSessions = null; 826 for (int i = 0; i < size; i++) { 827 final Session previousSession = mSessions.valueAt(i); 828 if (previousSession.taskId == session.taskId && previousSession.id != session.id 829 && (previousSession.getSaveInfoFlagsLocked() & SaveInfo.FLAG_DELAY_SAVE) != 0) { 830 if (previousSessions == null) { 831 previousSessions = new ArrayList<>(size); 832 } 833 previousSessions.add(previousSession); 834 } 835 } 836 // TODO(b/113281366): remove returned sessions / add CTS test 837 return previousSessions; 838 } 839 handleSessionSave(Session session)840 void handleSessionSave(Session session) { 841 synchronized (mLock) { 842 if (mSessions.get(session.id) == null) { 843 Slog.w(TAG, "handleSessionSave(): already gone: " + session.id); 844 845 return; 846 } 847 session.callSaveLocked(); 848 } 849 } 850 onPendingSaveUi(int operation, @NonNull IBinder token)851 void onPendingSaveUi(int operation, @NonNull IBinder token) { 852 if (sVerbose) Slog.v(TAG, "onPendingSaveUi(" + operation + "): " + token); 853 synchronized (mLock) { 854 final int sessionCount = mSessions.size(); 855 for (int i = sessionCount - 1; i >= 0; i--) { 856 final Session session = mSessions.valueAt(i); 857 if (session.isSaveUiPendingForTokenLocked(token)) { 858 session.onPendingSaveUi(operation, token); 859 return; 860 } 861 } 862 } 863 if (sDebug) { 864 Slog.d(TAG, "No pending Save UI for token " + token + " and operation " 865 + DebugUtils.flagsToString(AutofillManager.class, "PENDING_UI_OPERATION_", 866 operation)); 867 } 868 } 869 870 @GuardedBy("mLock") notifyImeAnimationStart(int sessionId, long startTimeMs, int uid)871 public void notifyImeAnimationStart(int sessionId, long startTimeMs, int uid) { 872 if (!isEnabledLocked()) { 873 Slog.wtf(TAG, "Service not enabled"); 874 return; 875 } 876 final Session session = mSessions.get(sessionId); 877 if (session == null || uid != session.uid) { 878 Slog.v(TAG, "notifyImeAnimationStart(): no session for " 879 + sessionId + "(" + uid + ")"); 880 return; 881 } 882 session.notifyImeAnimationStart(startTimeMs); 883 } 884 885 @GuardedBy("mLock") notifyImeAnimationEnd(int sessionId, long endTimeMs, int uid)886 public void notifyImeAnimationEnd(int sessionId, long endTimeMs, int uid) { 887 if (!isEnabledLocked()) { 888 Slog.wtf(TAG, "Service not enabled"); 889 return; 890 } 891 final Session session = mSessions.get(sessionId); 892 if (session == null || uid != session.uid) { 893 Slog.v(TAG, "notifyImeAnimationEnd(): no session for " 894 + sessionId + "(" + uid + ")"); 895 return; 896 } 897 session.notifyImeAnimationEnd(endTimeMs); 898 } 899 900 @GuardedBy("mLock") 901 @Override // from PerUserSystemService handlePackageUpdateLocked(@onNull String packageName)902 protected void handlePackageUpdateLocked(@NonNull String packageName) { 903 final ServiceInfo serviceInfo = mFieldClassificationStrategy.getServiceInfo(); 904 if (serviceInfo != null && serviceInfo.packageName.equals(packageName)) { 905 resetExtServiceLocked(); 906 } 907 } 908 909 @GuardedBy("mLock") resetExtServiceLocked()910 void resetExtServiceLocked() { 911 if (sVerbose) Slog.v(TAG, "reset autofill service in ExtServices."); 912 mFieldClassificationStrategy.reset(); 913 if (mRemoteInlineSuggestionRenderService != null) { 914 mRemoteInlineSuggestionRenderService.destroy(); 915 mRemoteInlineSuggestionRenderService = null; 916 } 917 } 918 919 @GuardedBy("mLock") destroyLocked()920 void destroyLocked() { 921 if (sVerbose) Slog.v(TAG, "destroyLocked()"); 922 923 resetExtServiceLocked(); 924 925 final int numSessions = mSessions.size(); 926 final ArraySet<RemoteFillService> remoteFillServices = new ArraySet<>(numSessions); 927 for (int i = 0; i < numSessions; i++) { 928 final RemoteFillService remoteFillService = mSessions.valueAt(i).destroyLocked(); 929 if (remoteFillService != null) { 930 remoteFillServices.add(remoteFillService); 931 } 932 } 933 mSessions.clear(); 934 if (AutofillFeatureFlags.isMultipleFillEventHistoryEnabled()) { 935 mFillHistories.clear(); 936 } 937 938 for (int i = 0; i < remoteFillServices.size(); i++) { 939 remoteFillServices.valueAt(i).destroy(); 940 } 941 942 sendStateToClients(/* resetclient=*/ true); 943 if (mClients != null) { 944 mClients.kill(); 945 mClients = null; 946 } 947 } 948 949 /** 950 * Initializes the last fill selection after an autofill service returned a new 951 * {@link FillResponse}. 952 */ 953 @GuardedBy("mLock") setLastResponseLocked(int sessionId, @NonNull FillResponse response)954 void setLastResponseLocked(int sessionId, @NonNull FillResponse response) { 955 mEventHistory = new FillEventHistory(sessionId, response.getClientState()); 956 } 957 setLastAugmentedAutofillResponse(int sessionId)958 void setLastAugmentedAutofillResponse(int sessionId) { 959 synchronized (mLock) { 960 mAugmentedAutofillEventHistory = new FillEventHistory(sessionId, /* clientState= */ 961 null); 962 } 963 } 964 965 /** 966 * Resets the last fill selection. 967 */ resetLastResponse()968 void resetLastResponse() { 969 synchronized (mLock) { 970 mEventHistory = null; 971 } 972 } 973 resetLastAugmentedAutofillResponse()974 void resetLastAugmentedAutofillResponse() { 975 synchronized (mLock) { 976 mAugmentedAutofillEventHistory = null; 977 } 978 } 979 980 @GuardedBy("mLock") isValidEventLocked(String method, int sessionId)981 private boolean isValidEventLocked(String method, int sessionId) { 982 if (mEventHistory == null) { 983 Slog.w(TAG, method + ": not logging event because history is null"); 984 return false; 985 } 986 if (sessionId != mEventHistory.getSessionId()) { 987 if (sDebug) { 988 Slog.d(TAG, method + ": not logging event for session " + sessionId 989 + " because tracked session is " + mEventHistory.getSessionId()); 990 } 991 return false; 992 } 993 return true; 994 } 995 996 @GuardedBy("mLock") addEventToHistory(String eventName, int sessionId, Event event)997 void addEventToHistory(String eventName, int sessionId, Event event) { 998 // For the singleton filleventhistory 999 if (isValidEventLocked(eventName, sessionId)) { 1000 mEventHistory.addEvent(event); 1001 } 1002 1003 if (AutofillFeatureFlags.isMultipleFillEventHistoryEnabled()) { 1004 FillEventHistory history = mFillHistories.get(sessionId); 1005 if (history != null) { 1006 history.addEvent(event); 1007 } else { 1008 if (sVerbose) { 1009 Slog.v(TAG, eventName 1010 + " not logged because FillEventHistory is not tracked for: " 1011 + sessionId); 1012 } 1013 } 1014 } 1015 } 1016 1017 /** 1018 * Updates the last fill selection when an authentication was selected. 1019 */ setAuthenticationSelected(int sessionId, @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId, boolean shouldAdd)1020 void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState, 1021 int uiType, @Nullable AutofillId focusedId, boolean shouldAdd) { 1022 synchronized (mLock) { 1023 1024 String methodName = "setAuthenticationSelected()"; 1025 1026 if (!shouldAdd) { 1027 if (sVerbose) { 1028 Slog.v(TAG, methodName + " not logged because shouldAdd is false"); 1029 } 1030 return; 1031 } 1032 1033 Event event = 1034 new Event( 1035 Event.TYPE_AUTHENTICATION_SELECTED, 1036 null, 1037 clientState, 1038 null, 1039 null, 1040 null, 1041 null, 1042 null, 1043 null, 1044 null, 1045 null, 1046 NO_SAVE_UI_REASON_NONE, 1047 uiType, 1048 focusedId); 1049 1050 addEventToHistory(methodName, sessionId, event); 1051 } 1052 } 1053 1054 /** Updates the last fill selection when a dataset authentication was selected. */ logDatasetAuthenticationSelected( @ullable String selectedDataset, int sessionId, @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId, boolean shouldAdd)1055 void logDatasetAuthenticationSelected( 1056 @Nullable String selectedDataset, 1057 int sessionId, 1058 @Nullable Bundle clientState, 1059 int uiType, 1060 @Nullable AutofillId focusedId, 1061 boolean shouldAdd) { 1062 synchronized (mLock) { 1063 String methodName = "logDatasetAuthenticationSelected()"; 1064 1065 if (!shouldAdd) { 1066 if (sVerbose) { 1067 Slog.v(TAG, methodName + " not logged because shouldAdd is false"); 1068 } 1069 return; 1070 } 1071 1072 Event event = new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset, 1073 clientState, null, null, null, null, null, null, null, null, 1074 NO_SAVE_UI_REASON_NONE, uiType, focusedId); 1075 addEventToHistory(methodName, sessionId, event); 1076 } 1077 } 1078 1079 /** 1080 * Updates the last fill selection when an save Ui is shown. 1081 */ logSaveShown(int sessionId, @Nullable Bundle clientState, boolean shouldAdd)1082 void logSaveShown(int sessionId, @Nullable Bundle clientState, boolean shouldAdd) { 1083 synchronized (mLock) { 1084 String methodName = "logSaveShown()"; 1085 1086 if (!shouldAdd) { 1087 if (sVerbose) { 1088 Slog.v(TAG, methodName + " not logged because shouldAdd is false"); 1089 } 1090 return; 1091 } 1092 1093 Event event = new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null, 1094 null, null, null, null, null, null, null, /* focusedId= */ null); 1095 1096 addEventToHistory(methodName, sessionId, event); 1097 } 1098 } 1099 1100 /** Updates the last fill response when a dataset was selected. */ logDatasetSelected( @ullable String selectedDataset, int sessionId, @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId, boolean shouldAdd)1101 void logDatasetSelected( 1102 @Nullable String selectedDataset, 1103 int sessionId, 1104 @Nullable Bundle clientState, 1105 int uiType, 1106 @Nullable AutofillId focusedId, 1107 boolean shouldAdd) { 1108 synchronized (mLock) { 1109 String methodName = "logDatasetSelected()"; 1110 1111 if (!shouldAdd) { 1112 if (sVerbose) { 1113 Slog.v(TAG, methodName + " not logged because shouldAdd is false"); 1114 } 1115 return; 1116 } 1117 1118 Event event = new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null, 1119 null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE, 1120 uiType, focusedId); 1121 addEventToHistory(methodName, sessionId, event); 1122 } 1123 } 1124 1125 /** 1126 * Updates the last fill response when a dataset is shown. 1127 */ logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId, boolean shouldAdd)1128 void logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType, 1129 @Nullable AutofillId focusedId, boolean shouldAdd) { 1130 synchronized (mLock) { 1131 String methodName = "logDatasetShown()"; 1132 1133 if (!shouldAdd) { 1134 if (sVerbose) { 1135 Slog.v(TAG, methodName + " not logged because shouldAdd is false"); 1136 } 1137 return; 1138 } 1139 1140 Event event = new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null, 1141 null, null, null, null, null, NO_SAVE_UI_REASON_NONE, 1142 uiType, focusedId); 1143 addEventToHistory(methodName, sessionId, event); 1144 } 1145 } 1146 logViewEnteredForHistory( int sessionId, @Nullable Bundle clientState, FillEventHistory history, @Nullable AutofillId focusedId)1147 void logViewEnteredForHistory( 1148 int sessionId, 1149 @Nullable Bundle clientState, 1150 FillEventHistory history, 1151 @Nullable AutofillId focusedId) { 1152 if (history.getEvents() != null) { 1153 // Do not log this event more than once 1154 for (Event event : history.getEvents()) { 1155 if (event.getType() == Event.TYPE_VIEW_REQUESTED_AUTOFILL) { 1156 if (sVerbose) { 1157 Slog.v(TAG, "logViewEntered: already logged TYPE_VIEW_REQUESTED_AUTOFILL"); 1158 } 1159 return; 1160 } 1161 } 1162 } 1163 1164 history.addEvent( 1165 new Event(Event.TYPE_VIEW_REQUESTED_AUTOFILL, null, clientState, null, 1166 null, null, null, null, null, null, null, focusedId)); 1167 } 1168 1169 /** 1170 * Updates the last fill response when a view was entered. 1171 */ logViewEntered(int sessionId, @Nullable Bundle clientState, @Nullable AutofillId focusedId, boolean shouldAdd)1172 void logViewEntered(int sessionId, @Nullable Bundle clientState, 1173 @Nullable AutofillId focusedId, boolean shouldAdd) { 1174 synchronized (mLock) { 1175 String methodName = "logViewEntered()"; 1176 1177 if (!shouldAdd) { 1178 if (sVerbose) { 1179 Slog.v(TAG, methodName + " not logged because shouldAdd is false"); 1180 } 1181 return; 1182 } 1183 1184 // This log does not call addEventToHistory() because each distinct FillEventHistory 1185 // can only contain 1 TYPE_VIEW_REQUESTED_AUTOFILL event. Therefore, checking both 1186 // the singleton FillEventHistory and the per Session FillEventHistory is necessary 1187 1188 if (isValidEventLocked(methodName, sessionId)) { 1189 logViewEnteredForHistory(sessionId, clientState, mEventHistory, focusedId); 1190 } 1191 1192 if (AutofillFeatureFlags.isMultipleFillEventHistoryEnabled()) { 1193 FillEventHistory history = mFillHistories.get(sessionId); 1194 if (history != null) { 1195 logViewEnteredForHistory(sessionId, clientState, history, focusedId); 1196 } 1197 } 1198 } 1199 } 1200 logAugmentedAutofillAuthenticationSelected(int sessionId, @Nullable String selectedDataset, @Nullable Bundle clientState)1201 void logAugmentedAutofillAuthenticationSelected(int sessionId, @Nullable String selectedDataset, 1202 @Nullable Bundle clientState) { 1203 synchronized (mLock) { 1204 if (mAugmentedAutofillEventHistory == null 1205 || mAugmentedAutofillEventHistory.getSessionId() != sessionId) { 1206 return; 1207 } 1208 mAugmentedAutofillEventHistory.addEvent( 1209 new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset, 1210 clientState, null, null, null, null, null, null, null, null, 1211 /* focusedId= */ null)); 1212 } 1213 } 1214 logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, @Nullable Bundle clientState)1215 void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, 1216 @Nullable Bundle clientState) { 1217 synchronized (mLock) { 1218 if (mAugmentedAutofillEventHistory == null 1219 || mAugmentedAutofillEventHistory.getSessionId() != sessionId) { 1220 return; 1221 } 1222 mAugmentedAutofillEventHistory.addEvent( 1223 new Event(Event.TYPE_DATASET_SELECTED, suggestionId, clientState, null, null, 1224 null, null, null, null, null, null, /* focusedId= */ null)); 1225 } 1226 } 1227 logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState)1228 void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState) { 1229 synchronized (mLock) { 1230 if (mAugmentedAutofillEventHistory == null 1231 || mAugmentedAutofillEventHistory.getSessionId() != sessionId) { 1232 return; 1233 } 1234 // Augmented Autofill only logs for inline now, so set UI_TYPE_INLINE here. 1235 // Ideally should not hardcode here and should also log for menu presentation. 1236 mAugmentedAutofillEventHistory.addEvent( 1237 new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null, 1238 null, null, null, null, null, NO_SAVE_UI_REASON_NONE, 1239 UI_TYPE_INLINE, /* focusedId= */ null)); 1240 1241 } 1242 } 1243 1244 /** 1245 * Updates the last fill response when an autofill context is committed. 1246 */ 1247 @GuardedBy("mLock") logContextCommittedLocked(int sessionId, @Nullable Bundle clientState, @Nullable ArrayList<String> selectedDatasets, @Nullable ArraySet<String> ignoredDatasets, @Nullable ArrayList<AutofillId> changedFieldIds, @Nullable ArrayList<String> changedDatasetIds, @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, @NonNull ComponentName appComponentName, boolean compatMode, boolean shouldAdd)1248 void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState, 1249 @Nullable ArrayList<String> selectedDatasets, 1250 @Nullable ArraySet<String> ignoredDatasets, 1251 @Nullable ArrayList<AutofillId> changedFieldIds, 1252 @Nullable ArrayList<String> changedDatasetIds, 1253 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, 1254 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, 1255 @NonNull ComponentName appComponentName, boolean compatMode, boolean shouldAdd) { 1256 logContextCommittedLocked(sessionId, clientState, selectedDatasets, ignoredDatasets, 1257 changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, 1258 manuallyFilledDatasetIds, /* detectedFieldIdsList= */ null, 1259 /* detectedFieldClassificationsList= */ null, appComponentName, compatMode, 1260 Event.NO_SAVE_UI_REASON_NONE, shouldAdd); 1261 } 1262 1263 @GuardedBy("mLock") logContextCommittedLocked(int sessionId, @Nullable Bundle clientState, @Nullable ArrayList<String> selectedDatasets, @Nullable ArraySet<String> ignoredDatasets, @Nullable ArrayList<AutofillId> changedFieldIds, @Nullable ArrayList<String> changedDatasetIds, @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, @Nullable ArrayList<AutofillId> detectedFieldIdsList, @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList, @NonNull ComponentName appComponentName, boolean compatMode, @NoSaveReason int saveDialogNotShowReason, boolean shouldAdd)1264 void logContextCommittedLocked(int sessionId, @Nullable Bundle clientState, 1265 @Nullable ArrayList<String> selectedDatasets, 1266 @Nullable ArraySet<String> ignoredDatasets, 1267 @Nullable ArrayList<AutofillId> changedFieldIds, 1268 @Nullable ArrayList<String> changedDatasetIds, 1269 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, 1270 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, 1271 @Nullable ArrayList<AutofillId> detectedFieldIdsList, 1272 @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList, 1273 @NonNull ComponentName appComponentName, boolean compatMode, 1274 @NoSaveReason int saveDialogNotShowReason, 1275 boolean shouldAdd) { 1276 1277 String methodName = "logContextCommittedLocked()"; 1278 1279 if (!shouldAdd) { 1280 if (sVerbose) { 1281 Slog.v(TAG, methodName + " not logged because shouldAdd is false"); 1282 } 1283 return; 1284 } 1285 1286 if (sVerbose) { 1287 Slog.v(TAG, "logContextCommitted() with FieldClassification: id=" + sessionId 1288 + ", selectedDatasets=" + selectedDatasets 1289 + ", ignoredDatasetIds=" + ignoredDatasets 1290 + ", changedAutofillIds=" + changedFieldIds 1291 + ", changedDatasetIds=" + changedDatasetIds 1292 + ", manuallyFilledFieldIds=" + manuallyFilledFieldIds 1293 + ", detectedFieldIds=" + detectedFieldIdsList 1294 + ", detectedFieldClassifications=" + detectedFieldClassificationsList 1295 + ", appComponentName=" + appComponentName.toShortString() 1296 + ", compatMode=" + compatMode 1297 + ", saveDialogNotShowReason=" + saveDialogNotShowReason); 1298 } 1299 1300 AutofillId[] detectedFieldsIds = null; 1301 FieldClassification[] detectedFieldClassifications = null; 1302 if (detectedFieldIdsList != null) { 1303 detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()]; 1304 detectedFieldIdsList.toArray(detectedFieldsIds); 1305 detectedFieldClassifications = 1306 new FieldClassification[detectedFieldClassificationsList.size()]; 1307 detectedFieldClassificationsList.toArray(detectedFieldClassifications); 1308 1309 final int numberFields = detectedFieldsIds.length; 1310 int totalSize = 0; 1311 float totalScore = 0; 1312 for (int i = 0; i < numberFields; i++) { 1313 final FieldClassification fc = detectedFieldClassifications[i]; 1314 final List<Match> matches = fc.getMatches(); 1315 final int size = matches.size(); 1316 totalSize += size; 1317 for (int j = 0; j < size; j++) { 1318 totalScore += matches.get(j).getScore(); 1319 } 1320 } 1321 1322 final int averageScore = (int) ((totalScore * 100) / totalSize); 1323 mMetricsLogger.write( 1324 Helper.newLogMaker( 1325 MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES, 1326 appComponentName, 1327 getServicePackageName(), 1328 sessionId, 1329 compatMode) 1330 .setCounterValue(numberFields) 1331 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, averageScore)); 1332 } 1333 Event event = 1334 new Event( 1335 Event.TYPE_CONTEXT_COMMITTED, 1336 null, 1337 clientState, 1338 selectedDatasets, 1339 ignoredDatasets, 1340 changedFieldIds, 1341 changedDatasetIds, 1342 manuallyFilledFieldIds, 1343 manuallyFilledDatasetIds, 1344 detectedFieldsIds, 1345 detectedFieldClassifications, 1346 saveDialogNotShowReason, 1347 /* focusedId= */ null); 1348 1349 addEventToHistory(methodName, sessionId, event); 1350 } 1351 1352 /** 1353 * Gets the fill event history. 1354 * 1355 * @param callingUid The calling uid 1356 * @return The history for the autofill or the augmented autofill events depending on the {@code 1357 * callingUid}, or {@code null} if there is none. 1358 * @deprecated Use {@link 1359 * android.service.autofill.AutofillService#onSessionDestroyed(FillEventHistory)} 1360 */ getFillEventHistory(int callingUid)1361 FillEventHistory getFillEventHistory(int callingUid) { 1362 synchronized (mLock) { 1363 if (mEventHistory != null 1364 && isCalledByServiceLocked("getFillEventHistory", callingUid)) { 1365 return mEventHistory; 1366 } 1367 if (mAugmentedAutofillEventHistory != null && isCalledByAugmentedAutofillServiceLocked( 1368 "getFillEventHistory", callingUid)) { 1369 return mAugmentedAutofillEventHistory; 1370 } 1371 } 1372 return null; 1373 } 1374 1375 // Called by Session - does not need to check uid getUserData()1376 UserData getUserData() { 1377 synchronized (mLock) { 1378 return mUserData; 1379 } 1380 } 1381 1382 // Called by AutofillManager getUserData(int callingUid)1383 UserData getUserData(int callingUid) { 1384 synchronized (mLock) { 1385 if (isCalledByServiceLocked("getUserData", callingUid)) { 1386 return mUserData; 1387 } 1388 } 1389 return null; 1390 } 1391 1392 // Called by AutofillManager setUserData(int callingUid, UserData userData)1393 void setUserData(int callingUid, UserData userData) { 1394 synchronized (mLock) { 1395 if (!isCalledByServiceLocked("setUserData", callingUid)) { 1396 return; 1397 } 1398 mUserData = userData; 1399 // Log it 1400 final int numberFields = mUserData == null ? 0: mUserData.getCategoryIds().length; 1401 // NOTE: contrary to most metrics, the service name is logged as the main package name 1402 // here, not as MetricsEvent.FIELD_AUTOFILL_SERVICE 1403 mMetricsLogger.write(new LogMaker(MetricsEvent.AUTOFILL_USERDATA_UPDATED) 1404 .setPackageName(getServicePackageName()) 1405 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, numberFields)); 1406 } 1407 } 1408 1409 @GuardedBy("mLock") isCalledByServiceLocked(@onNull String methodName, int callingUid)1410 private boolean isCalledByServiceLocked(@NonNull String methodName, int callingUid) { 1411 final int serviceUid = getServiceUidLocked(); 1412 if (serviceUid != callingUid) { 1413 Slog.w(TAG, methodName + "() called by UID " + callingUid 1414 + ", but service UID is " + serviceUid); 1415 return false; 1416 } 1417 return true; 1418 } 1419 1420 @GuardedBy("mLock") getSupportedSmartSuggestionModesLocked()1421 @SmartSuggestionMode int getSupportedSmartSuggestionModesLocked() { 1422 return mMaster.getSupportedSmartSuggestionModesLocked(); 1423 } 1424 1425 @Override 1426 @GuardedBy("mLock") dumpLocked(String prefix, PrintWriter pw)1427 protected void dumpLocked(String prefix, PrintWriter pw) { 1428 super.dumpLocked(prefix, pw); 1429 1430 final String prefix2 = prefix + " "; 1431 1432 pw.print(prefix); pw.print("UID: "); pw.println(getServiceUidLocked()); 1433 pw.print(prefix); pw.print("Autofill Service Info: "); 1434 if (mInfo == null) { 1435 pw.println("N/A"); 1436 } else { 1437 pw.println(); 1438 mInfo.dump(prefix2, pw); 1439 } 1440 pw.print(prefix); pw.print("Default component: "); pw.println(getContext() 1441 .getString(R.string.config_defaultAutofillService)); 1442 pw.println(); 1443 1444 pw.print(prefix); pw.println("mAugmentedAutofillName: "); 1445 pw.print(prefix2); mMaster.mAugmentedAutofillResolver.dumpShort(pw, mUserId); 1446 pw.println(); 1447 if (mRemoteAugmentedAutofillService != null) { 1448 pw.print(prefix); pw.println("RemoteAugmentedAutofillService: "); 1449 mRemoteAugmentedAutofillService.dump(prefix2, pw); 1450 } 1451 if (mRemoteAugmentedAutofillServiceInfo != null) { 1452 pw.print(prefix); pw.print("RemoteAugmentedAutofillServiceInfo: "); 1453 pw.println(mRemoteAugmentedAutofillServiceInfo); 1454 } 1455 pw.println(); 1456 1457 pw.print(prefix); pw.println("mFieldClassificationService for system detection"); 1458 pw.print(prefix2); pw.print("Default component: "); pw.println(getContext() 1459 .getString(R.string.config_defaultFieldClassificationService)); 1460 pw.print(prefix2); mMaster.mFieldClassificationResolver.dumpShort(pw, mUserId); 1461 pw.println(); 1462 1463 if (mRemoteFieldClassificationService != null) { 1464 pw.print(prefix); pw.println("RemoteFieldClassificationService: "); 1465 mRemoteFieldClassificationService.dump(prefix2, pw); 1466 } else { 1467 pw.print(prefix); pw.println("mRemoteFieldClassificationService: null"); 1468 } 1469 if (mRemoteFieldClassificationServiceInfo != null) { 1470 pw.print(prefix); pw.print("RemoteFieldClassificationServiceInfo: "); 1471 pw.println(mRemoteFieldClassificationServiceInfo); 1472 } else { 1473 pw.print(prefix); pw.println("mRemoteFieldClassificationServiceInfo: null"); 1474 } 1475 pw.println(); 1476 1477 pw.print(prefix); pw.print("Field classification enabled: "); 1478 pw.println(isFieldClassificationEnabledLocked()); 1479 pw.print(prefix); pw.print("Compat pkgs: "); 1480 final ArrayMap<String, Long> compatPkgs = getCompatibilityPackagesLocked(); 1481 if (compatPkgs == null) { 1482 pw.println("N/A"); 1483 } else { 1484 pw.println(compatPkgs); 1485 } 1486 pw.print(prefix); pw.print("Inline Suggestions Enabled: "); 1487 pw.println(isInlineSuggestionsEnabledLocked()); 1488 pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune); 1489 1490 mDisabledInfoCache.dump(mUserId, prefix, pw); 1491 1492 final int size = mSessions.size(); 1493 if (size == 0) { 1494 pw.print(prefix); pw.println("No sessions"); 1495 } else { 1496 pw.print(prefix); pw.print(size); pw.println(" sessions:"); 1497 for (int i = 0; i < size; i++) { 1498 pw.print(prefix); pw.print("#"); pw.println(i + 1); 1499 mSessions.valueAt(i).dumpLocked(prefix2, pw); 1500 } 1501 } 1502 1503 pw.print(prefix); pw.print("Clients: "); 1504 if (mClients == null) { 1505 pw.println("N/A"); 1506 } else { 1507 pw.println(); 1508 mClients.dump(pw, prefix2); 1509 } 1510 1511 if (mEventHistory == null || mEventHistory.getEvents() == null 1512 || mEventHistory.getEvents().size() == 0) { 1513 pw.print(prefix); pw.println("No event on last fill response"); 1514 } else { 1515 pw.print(prefix); pw.println("Events of last fill response:"); 1516 pw.print(prefix); 1517 1518 int numEvents = mEventHistory.getEvents().size(); 1519 for (int i = 0; i < numEvents; i++) { 1520 final Event event = mEventHistory.getEvents().get(i); 1521 pw.println(" " + i + ": eventType=" + event.getType() + " datasetId=" 1522 + event.getDatasetId()); 1523 } 1524 } 1525 1526 pw.print(prefix); pw.print("User data: "); 1527 if (mUserData == null) { 1528 pw.println("N/A"); 1529 } else { 1530 pw.println(); 1531 mUserData.dump(prefix2, pw); 1532 } 1533 1534 pw.print(prefix); pw.println("Field Classification strategy: "); 1535 mFieldClassificationStrategy.dump(prefix2, pw); 1536 } 1537 1538 @GuardedBy("mLock") forceRemoveAllSessionsLocked()1539 void forceRemoveAllSessionsLocked() { 1540 final int sessionCount = mSessions.size(); 1541 if (sessionCount == 0) { 1542 mUi.destroyAll(null, null, false); 1543 return; 1544 } 1545 1546 for (int i = sessionCount - 1; i >= 0; i--) { 1547 mSessions.valueAt(i).forceRemoveFromServiceLocked(); 1548 } 1549 } 1550 1551 @GuardedBy("mLock") forceRemoveForAugmentedOnlySessionsLocked()1552 void forceRemoveForAugmentedOnlySessionsLocked() { 1553 final int sessionCount = mSessions.size(); 1554 for (int i = sessionCount - 1; i >= 0; i--) { 1555 mSessions.valueAt(i).forceRemoveFromServiceIfForAugmentedOnlyLocked(); 1556 } 1557 } 1558 1559 /** 1560 * This method is called exclusively in response to {@code Intent.ACTION_CLOSE_SYSTEM_DIALOGS}. 1561 * The method removes all sessions that are finished but showing SaveUI due to how SaveUI is 1562 * managed (see b/64940307). Otherwise it will remove any augmented autofill generated windows. 1563 */ 1564 // TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities 1565 @GuardedBy("mLock") forceRemoveFinishedSessionsLocked()1566 void forceRemoveFinishedSessionsLocked() { 1567 final int sessionCount = mSessions.size(); 1568 for (int i = sessionCount - 1; i >= 0; i--) { 1569 final Session session = mSessions.valueAt(i); 1570 if (session.isSaveUiShowingLocked()) { 1571 if (sDebug) Slog.d(TAG, "destroyFinishedSessionsLocked(): " + session.id); 1572 session.forceRemoveFromServiceLocked(); 1573 } else { 1574 session.destroyAugmentedAutofillWindowsLocked(); 1575 } 1576 } 1577 } 1578 1579 @GuardedBy("mLock") listSessionsLocked(ArrayList<String> output)1580 void listSessionsLocked(ArrayList<String> output) { 1581 final int numSessions = mSessions.size(); 1582 if (numSessions <= 0) return; 1583 1584 final String fmt = "%d:%s:%s"; 1585 for (int i = 0; i < numSessions; i++) { 1586 final int id = mSessions.keyAt(i); 1587 final String service = mInfo == null 1588 ? "no_svc" 1589 : mInfo.getServiceInfo().getComponentName().flattenToShortString(); 1590 final String augmentedService = mRemoteAugmentedAutofillServiceInfo == null 1591 ? "no_aug" 1592 : mRemoteAugmentedAutofillServiceInfo.getComponentName().flattenToShortString(); 1593 output.add(String.format(fmt, id, service, augmentedService)); 1594 } 1595 } 1596 1597 @GuardedBy("mLock") getCompatibilityPackagesLocked()1598 @Nullable ArrayMap<String, Long> getCompatibilityPackagesLocked() { 1599 if (mInfo != null) { 1600 return mInfo.getCompatibilityPackages(); 1601 } 1602 return null; 1603 } 1604 1605 @GuardedBy("mLock") isInlineSuggestionsEnabledLocked()1606 boolean isInlineSuggestionsEnabledLocked() { 1607 if (mInfo != null) { 1608 return mInfo.isInlineSuggestionsEnabled(); 1609 } 1610 return false; 1611 } 1612 1613 @GuardedBy("mLock") requestSavedPasswordCount(IResultReceiver receiver)1614 void requestSavedPasswordCount(IResultReceiver receiver) { 1615 RemoteFillService remoteService = 1616 new RemoteFillService( 1617 getContext(), mInfo.getServiceInfo().getComponentName(), mUserId, 1618 /* callbacks= */ null, mMaster.isInstantServiceAllowed(), 1619 mMaster.mCredentialAutofillService); 1620 remoteService.onSavedPasswordCountRequest(receiver); 1621 } 1622 1623 @GuardedBy("mLock") getRemoteAugmentedAutofillServiceLocked()1624 @Nullable RemoteAugmentedAutofillService getRemoteAugmentedAutofillServiceLocked() { 1625 if (mRemoteAugmentedAutofillService == null) { 1626 final String serviceName = mMaster.mAugmentedAutofillResolver.getServiceName(mUserId); 1627 if (serviceName == null) { 1628 if (mMaster.verbose) { 1629 Slog.v(TAG, "getRemoteAugmentedAutofillServiceLocked(): not set"); 1630 } 1631 return null; 1632 } 1633 final Pair<ServiceInfo, ComponentName> pair = RemoteAugmentedAutofillService 1634 .getComponentName(serviceName, mUserId, 1635 mMaster.mAugmentedAutofillResolver.isTemporary(mUserId)); 1636 if (pair == null) return null; 1637 1638 mRemoteAugmentedAutofillServiceInfo = pair.first; 1639 final ComponentName componentName = pair.second; 1640 if (sVerbose) { 1641 Slog.v(TAG, "getRemoteAugmentedAutofillServiceLocked(): " + componentName); 1642 } 1643 1644 final RemoteAugmentedAutofillServiceCallbacks callbacks = 1645 new RemoteAugmentedAutofillServiceCallbacks() { 1646 @Override 1647 public void resetLastResponse() { 1648 AutofillManagerServiceImpl.this.resetLastAugmentedAutofillResponse(); 1649 } 1650 1651 @Override 1652 public void setLastResponse(int sessionId) { 1653 AutofillManagerServiceImpl.this.setLastAugmentedAutofillResponse( 1654 sessionId); 1655 } 1656 1657 @Override 1658 public void logAugmentedAutofillShown(int sessionId, Bundle clientState) { 1659 AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId, 1660 clientState); 1661 } 1662 1663 @Override 1664 public void logAugmentedAutofillSelected(int sessionId, 1665 String suggestionId, Bundle clientState) { 1666 AutofillManagerServiceImpl.this.logAugmentedAutofillSelected(sessionId, 1667 suggestionId, clientState); 1668 } 1669 1670 @Override 1671 public void logAugmentedAutofillAuthenticationSelected(int sessionId, 1672 String suggestionId, Bundle clientState) { 1673 AutofillManagerServiceImpl.this 1674 .logAugmentedAutofillAuthenticationSelected( 1675 sessionId, suggestionId, clientState); 1676 } 1677 1678 @Override 1679 public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) { 1680 Slog.w(TAG, "remote augmented autofill service died"); 1681 final RemoteAugmentedAutofillService remoteService = 1682 mRemoteAugmentedAutofillService; 1683 if (remoteService != null) { 1684 remoteService.unbind(); 1685 } 1686 mRemoteAugmentedAutofillService = null; 1687 } 1688 }; 1689 final int serviceUid = mRemoteAugmentedAutofillServiceInfo.applicationInfo.uid; 1690 mRemoteAugmentedAutofillService = new RemoteAugmentedAutofillService(getContext(), 1691 serviceUid, componentName, 1692 mUserId, callbacks, mMaster.isInstantServiceAllowed(), 1693 mMaster.verbose, mMaster.mAugmentedServiceIdleUnbindTimeoutMs, 1694 mMaster.mAugmentedServiceRequestTimeoutMs); 1695 } 1696 1697 return mRemoteAugmentedAutofillService; 1698 } 1699 1700 @GuardedBy("mLock") getRemoteAugmentedAutofillServiceIfCreatedLocked()1701 @Nullable RemoteAugmentedAutofillService getRemoteAugmentedAutofillServiceIfCreatedLocked() { 1702 return mRemoteAugmentedAutofillService; 1703 } 1704 1705 /** 1706 * Called when the {@link AutofillManagerService#mAugmentedAutofillResolver} 1707 * changed (among other places). 1708 */ updateRemoteAugmentedAutofillService()1709 void updateRemoteAugmentedAutofillService() { 1710 synchronized (mLock) { 1711 if (mRemoteAugmentedAutofillService != null) { 1712 if (sVerbose) { 1713 Slog.v(TAG, "updateRemoteAugmentedAutofillService(): " 1714 + "destroying old remote service"); 1715 } 1716 forceRemoveForAugmentedOnlySessionsLocked(); 1717 mRemoteAugmentedAutofillService.unbind(); 1718 mRemoteAugmentedAutofillService = null; 1719 mRemoteAugmentedAutofillServiceInfo = null; 1720 resetAugmentedAutofillWhitelistLocked(); 1721 } 1722 1723 final boolean available = isAugmentedAutofillServiceAvailableLocked(); 1724 if (sVerbose) Slog.v(TAG, "updateRemoteAugmentedAutofillService(): " + available); 1725 1726 if (available) { 1727 mRemoteAugmentedAutofillService = getRemoteAugmentedAutofillServiceLocked(); 1728 } 1729 } 1730 } 1731 isAugmentedAutofillServiceAvailableLocked()1732 private boolean isAugmentedAutofillServiceAvailableLocked() { 1733 if (mMaster.verbose) { 1734 Slog.v(TAG, "isAugmentedAutofillService(): " 1735 + "setupCompleted=" + isSetupCompletedLocked() 1736 + ", disabled=" + isDisabledByUserRestrictionsLocked() 1737 + ", augmentedService=" 1738 + mMaster.mAugmentedAutofillResolver.getServiceName(mUserId)); 1739 } 1740 if (!isSetupCompletedLocked() || isDisabledByUserRestrictionsLocked() 1741 || mMaster.mAugmentedAutofillResolver.getServiceName(mUserId) == null) { 1742 return false; 1743 } 1744 return true; 1745 } 1746 isAugmentedAutofillServiceForUserLocked(int callingUid)1747 boolean isAugmentedAutofillServiceForUserLocked(int callingUid) { 1748 return mRemoteAugmentedAutofillServiceInfo != null 1749 && mRemoteAugmentedAutofillServiceInfo.applicationInfo.uid == callingUid; 1750 } 1751 1752 /** 1753 * Sets which packages and activities can trigger augmented autofill. 1754 * 1755 * @return whether caller UID is the augmented autofill service for the user 1756 */ 1757 @GuardedBy("mLock") setAugmentedAutofillWhitelistLocked(@ullable List<String> packages, @Nullable List<ComponentName> activities, int callingUid)1758 boolean setAugmentedAutofillWhitelistLocked(@Nullable List<String> packages, 1759 @Nullable List<ComponentName> activities, int callingUid) { 1760 1761 if (!isCalledByAugmentedAutofillServiceLocked("setAugmentedAutofillWhitelistLocked", 1762 callingUid)) { 1763 return false; 1764 } 1765 if (mMaster.verbose) { 1766 Slog.v(TAG, "setAugmentedAutofillWhitelistLocked(packages=" + packages + ", activities=" 1767 + activities + ")"); 1768 } 1769 allowlistForAugmentedAutofillPackages(packages, activities); 1770 final String serviceName; 1771 if (mRemoteAugmentedAutofillServiceInfo != null) { 1772 serviceName = mRemoteAugmentedAutofillServiceInfo.getComponentName() 1773 .flattenToShortString(); 1774 } else { 1775 Slog.e(TAG, "setAugmentedAutofillWhitelistLocked(): no service"); 1776 serviceName = "N/A"; 1777 } 1778 1779 final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_AUGMENTED_WHITELIST_REQUEST) 1780 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, serviceName); 1781 if (packages != null) { 1782 log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_PACKAGES, packages.size()); 1783 } 1784 if (activities != null) { 1785 log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_ACTIVITIES, activities.size()); 1786 } 1787 mMetricsLogger.write(log); 1788 1789 return true; 1790 } 1791 1792 @GuardedBy("mLock") isCalledByAugmentedAutofillServiceLocked(@onNull String methodName, int callingUid)1793 private boolean isCalledByAugmentedAutofillServiceLocked(@NonNull String methodName, 1794 int callingUid) { 1795 // Lazy load service first 1796 final RemoteAugmentedAutofillService service = getRemoteAugmentedAutofillServiceLocked(); 1797 if (service == null) { 1798 Slog.w(TAG, methodName + "() called by UID " + callingUid 1799 + ", but there is no augmented autofill service defined for user " 1800 + getUserId()); 1801 return false; 1802 } 1803 1804 if (getAugmentedAutofillServiceUidLocked() != callingUid) { 1805 Slog.w(TAG, methodName + "() called by UID " + callingUid 1806 + ", but service UID is " + getAugmentedAutofillServiceUidLocked() 1807 + " for user " + getUserId()); 1808 return false; 1809 } 1810 return true; 1811 } 1812 1813 @Nullable getCredentialAutofillService(Context context)1814 private ComponentName getCredentialAutofillService(Context context) { 1815 ComponentName componentName = null; 1816 String credentialManagerAutofillCompName = context.getResources().getString( 1817 R.string.config_defaultCredentialManagerAutofillService); 1818 if (credentialManagerAutofillCompName != null 1819 && !credentialManagerAutofillCompName.isEmpty()) { 1820 componentName = ComponentName.unflattenFromString( 1821 credentialManagerAutofillCompName); 1822 } 1823 if (componentName == null) { 1824 Slog.w(TAG, "Invalid CredentialAutofillService"); 1825 } 1826 return componentName; 1827 } 1828 1829 @GuardedBy("mLock") getAugmentedAutofillServiceUidLocked()1830 private int getAugmentedAutofillServiceUidLocked() { 1831 if (mRemoteAugmentedAutofillServiceInfo == null) { 1832 if (mMaster.verbose) { 1833 Slog.v(TAG, "getAugmentedAutofillServiceUid(): " 1834 + "no mRemoteAugmentedAutofillServiceInfo"); 1835 } 1836 return Process.INVALID_UID; 1837 } 1838 return mRemoteAugmentedAutofillServiceInfo.applicationInfo.uid; 1839 } 1840 1841 @GuardedBy("mLock") isWhitelistedForAugmentedAutofillLocked(@onNull ComponentName componentName)1842 boolean isWhitelistedForAugmentedAutofillLocked(@NonNull ComponentName componentName) { 1843 return mMaster.mAugmentedAutofillState.isWhitelisted(mUserId, componentName); 1844 } 1845 1846 /** 1847 * @throws IllegalArgumentException if packages or components are empty. 1848 */ allowlistForAugmentedAutofillPackages(@ullable List<String> packages, @Nullable List<ComponentName> components)1849 private void allowlistForAugmentedAutofillPackages(@Nullable List<String> packages, 1850 @Nullable List<ComponentName> components) { 1851 // TODO(b/123100824): add CTS test for when it's null 1852 synchronized (mLock) { 1853 if (mMaster.verbose) { 1854 Slog.v(TAG, "whitelisting packages: " + packages + "and activities: " + components); 1855 } 1856 mMaster.mAugmentedAutofillState.setWhitelist(mUserId, packages, components); 1857 } 1858 } 1859 1860 /** 1861 * Resets the augmented autofill allowlist. 1862 */ 1863 @GuardedBy("mLock") resetAugmentedAutofillWhitelistLocked()1864 void resetAugmentedAutofillWhitelistLocked() { 1865 if (mMaster.verbose) { 1866 Slog.v(TAG, "resetting augmented autofill whitelist"); 1867 } 1868 mMaster.mAugmentedAutofillState.resetWhitelist(mUserId); 1869 } 1870 sendStateToClients(boolean resetClient)1871 private void sendStateToClients(boolean resetClient) { 1872 final RemoteCallbackList<IAutoFillManagerClient> clients; 1873 final int userClientCount; 1874 synchronized (mLock) { 1875 if (mClients == null) { 1876 return; 1877 } 1878 clients = mClients; 1879 userClientCount = clients.beginBroadcast(); 1880 } 1881 try { 1882 for (int i = 0; i < userClientCount; i++) { 1883 final IAutoFillManagerClient client = clients.getBroadcastItem(i); 1884 try { 1885 final boolean resetSession; 1886 final boolean isEnabled; 1887 synchronized (mLock) { 1888 resetSession = resetClient || isClientSessionDestroyedLocked(client); 1889 isEnabled = isEnabledLocked(); 1890 } 1891 int flags = 0; 1892 if (isEnabled) { 1893 flags |= AutofillManager.SET_STATE_FLAG_ENABLED; 1894 } 1895 if (resetSession) { 1896 flags |= AutofillManager.SET_STATE_FLAG_RESET_SESSION; 1897 } 1898 if (resetClient) { 1899 flags |= AutofillManager.SET_STATE_FLAG_RESET_CLIENT; 1900 } 1901 if (sDebug) { 1902 flags |= AutofillManager.SET_STATE_FLAG_DEBUG; 1903 } 1904 if (sVerbose) { 1905 flags |= AutofillManager.SET_STATE_FLAG_VERBOSE; 1906 } 1907 client.setState(flags); 1908 } catch (RemoteException re) { 1909 /* ignore */ 1910 } 1911 } 1912 } finally { 1913 clients.finishBroadcast(); 1914 } 1915 } 1916 1917 @GuardedBy("mLock") isClientSessionDestroyedLocked(IAutoFillManagerClient client)1918 private boolean isClientSessionDestroyedLocked(IAutoFillManagerClient client) { 1919 final int sessionCount = mSessions.size(); 1920 for (int i = 0; i < sessionCount; i++) { 1921 final Session session = mSessions.valueAt(i); 1922 if (session.getClient().equals(client)) { 1923 return session.isDestroyed(); 1924 } 1925 } 1926 return true; 1927 } 1928 1929 /** 1930 * Called by {@link Session} when service asked to disable autofill for an app. 1931 */ disableAutofillForApp(@onNull String packageName, long duration, int sessionId, boolean compatMode)1932 void disableAutofillForApp(@NonNull String packageName, long duration, int sessionId, 1933 boolean compatMode) { 1934 synchronized (mLock) { 1935 long expiration = SystemClock.elapsedRealtime() + duration; 1936 // Protect it against overflow 1937 if (expiration < 0) { 1938 expiration = Long.MAX_VALUE; 1939 } 1940 mDisabledInfoCache.addDisabledAppLocked(mUserId, packageName, expiration); 1941 1942 int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration; 1943 mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP, 1944 packageName, getServicePackageName(), sessionId, compatMode) 1945 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration)); 1946 } 1947 } 1948 1949 /** 1950 * Called by {@link Session} when service asked to disable autofill an app. 1951 */ disableAutofillForActivity(@onNull ComponentName componentName, long duration, int sessionId, boolean compatMode)1952 void disableAutofillForActivity(@NonNull ComponentName componentName, long duration, 1953 int sessionId, boolean compatMode) { 1954 synchronized (mLock) { 1955 long expiration = SystemClock.elapsedRealtime() + duration; 1956 // Protect it against overflow 1957 if (expiration < 0) { 1958 expiration = Long.MAX_VALUE; 1959 } 1960 mDisabledInfoCache.addDisabledActivityLocked(mUserId, componentName, expiration); 1961 final int intDuration = duration > Integer.MAX_VALUE 1962 ? Integer.MAX_VALUE 1963 : (int) duration; 1964 1965 final LogMaker log = Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY, 1966 componentName, getServicePackageName(), sessionId, compatMode) 1967 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration); 1968 mMetricsLogger.write(log); 1969 } 1970 } 1971 1972 /** 1973 * Checks if autofill is disabled by service to the given activity. 1974 */ 1975 @GuardedBy("mLock") isAutofillDisabledLocked(@onNull ComponentName componentName)1976 private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) { 1977 return mDisabledInfoCache.isAutofillDisabledLocked(mUserId, componentName); 1978 } 1979 1980 // Called by AutofillManager, checks UID. isFieldClassificationEnabled(int callingUid)1981 boolean isFieldClassificationEnabled(int callingUid) { 1982 synchronized (mLock) { 1983 if (!isCalledByServiceLocked("isFieldClassificationEnabled", callingUid)) { 1984 return false; 1985 } 1986 return isFieldClassificationEnabledLocked(); 1987 } 1988 } 1989 1990 // Called by internally, no need to check UID. isFieldClassificationEnabledLocked()1991 boolean isFieldClassificationEnabledLocked() { 1992 return Settings.Secure.getIntForUser( 1993 getContext().getContentResolver(), 1994 Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION, 1, 1995 mUserId) == 1; 1996 } 1997 getFieldClassificationStrategy()1998 FieldClassificationStrategy getFieldClassificationStrategy() { 1999 return mFieldClassificationStrategy; 2000 } 2001 getAvailableFieldClassificationAlgorithms(int callingUid)2002 String[] getAvailableFieldClassificationAlgorithms(int callingUid) { 2003 synchronized (mLock) { 2004 if (!isCalledByServiceLocked("getFCAlgorithms()", callingUid)) { 2005 return null; 2006 } 2007 } 2008 return mFieldClassificationStrategy.getAvailableAlgorithms(); 2009 } 2010 getDefaultFieldClassificationAlgorithm(int callingUid)2011 String getDefaultFieldClassificationAlgorithm(int callingUid) { 2012 synchronized (mLock) { 2013 if (!isCalledByServiceLocked("getDefaultFCAlgorithm()", callingUid)) { 2014 return null; 2015 } 2016 } 2017 return mFieldClassificationStrategy.getDefaultAlgorithm(); 2018 } 2019 getRemoteInlineSuggestionRenderServiceLocked()2020 @Nullable RemoteInlineSuggestionRenderService getRemoteInlineSuggestionRenderServiceLocked() { 2021 if (mRemoteInlineSuggestionRenderService == null) { 2022 final ComponentName componentName = RemoteInlineSuggestionRenderService 2023 .getServiceComponentName(getContext(), mUserId); 2024 if (componentName == null) { 2025 Slog.w(TAG, "No valid component found for InlineSuggestionRenderService"); 2026 return null; 2027 } 2028 2029 mRemoteInlineSuggestionRenderService = new RemoteInlineSuggestionRenderService( 2030 getContext(), componentName, InlineSuggestionRenderService.SERVICE_INTERFACE, 2031 mUserId, new InlineSuggestionRenderCallbacksImpl(), 2032 mMaster.isBindInstantServiceAllowed(), mMaster.verbose); 2033 } 2034 2035 return mRemoteInlineSuggestionRenderService; 2036 } 2037 2038 private class InlineSuggestionRenderCallbacksImpl implements 2039 RemoteInlineSuggestionRenderService.InlineSuggestionRenderCallbacks { 2040 2041 @Override // from InlineSuggestionRenderCallbacksImpl onServiceDied(@onNull RemoteInlineSuggestionRenderService service)2042 public void onServiceDied(@NonNull RemoteInlineSuggestionRenderService service) { 2043 Slog.w(TAG, "remote service died: " + service); 2044 synchronized (mLock) { 2045 resetExtServiceLocked(); 2046 } 2047 } 2048 } 2049 onSwitchInputMethod()2050 void onSwitchInputMethod() { 2051 synchronized (mLock) { 2052 final int sessionCount = mSessions.size(); 2053 for (int i = 0; i < sessionCount; i++) { 2054 final Session session = mSessions.valueAt(i); 2055 session.onSwitchInputMethodLocked(); 2056 } 2057 } 2058 } 2059 2060 @GuardedBy("mLock") getRemoteFieldClassificationServiceLocked()2061 @Nullable RemoteFieldClassificationService getRemoteFieldClassificationServiceLocked() { 2062 if (mRemoteFieldClassificationService == null) { 2063 final String serviceName = mMaster.mFieldClassificationResolver.getServiceName(mUserId); 2064 if (serviceName == null) { 2065 if (mMaster.verbose) { 2066 Slog.v(TAG, "getRemoteFieldClassificationServiceLocked(): not set"); 2067 } 2068 return null; 2069 } 2070 if (sVerbose) { 2071 Slog.v(TAG, "getRemoteFieldClassificationServiceLocked serviceName: " 2072 + serviceName); 2073 } 2074 boolean sTemporaryFieldDetectionService = 2075 mMaster.mFieldClassificationResolver.isTemporary(mUserId); 2076 final Pair<ServiceInfo, ComponentName> pair = RemoteFieldClassificationService 2077 .getComponentName(serviceName, mUserId, sTemporaryFieldDetectionService); 2078 if (pair == null) { 2079 Slog.w(TAG, "RemoteFieldClassificationService.getComponentName returned null " 2080 + "with serviceName: " + serviceName); 2081 return null; 2082 } 2083 2084 mRemoteFieldClassificationServiceInfo = pair.first; 2085 final ComponentName componentName = pair.second; 2086 if (sVerbose) { 2087 Slog.v(TAG, "getRemoteFieldClassificationServiceLocked(): " + componentName); 2088 } 2089 final int serviceUid = mRemoteFieldClassificationServiceInfo.applicationInfo.uid; 2090 mRemoteFieldClassificationService = new RemoteFieldClassificationService(getContext(), 2091 componentName, serviceUid, mUserId); 2092 } 2093 2094 return mRemoteFieldClassificationService; 2095 } 2096 2097 @GuardedBy("mLock") 2098 @Nullable RemoteFieldClassificationService getRemoteFieldClassificationServiceIfCreatedLocked()2099 getRemoteFieldClassificationServiceIfCreatedLocked() { 2100 return mRemoteFieldClassificationService; 2101 } 2102 2103 isPccClassificationEnabled()2104 public boolean isPccClassificationEnabled() { 2105 boolean result = isPccClassificationEnabledInternal(); 2106 if (sVerbose) { 2107 Slog.v(TAG, "pccEnabled: " + result); 2108 } 2109 return result; 2110 } 2111 isPccClassificationEnabledInternal()2112 public boolean isPccClassificationEnabledInternal() { 2113 boolean flagEnabled = mMaster.isPccClassificationFlagEnabled(); 2114 if (!flagEnabled) return false; 2115 synchronized (mLock) { 2116 return getRemoteFieldClassificationServiceLocked() != null; 2117 } 2118 } 2119 isAutofillCredmanIntegrationEnabled()2120 public boolean isAutofillCredmanIntegrationEnabled() { 2121 return mMaster.isAutofillCredmanIntegrationEnabled(); 2122 } 2123 2124 /** 2125 * Called when the {@link AutofillManagerService#mFieldClassificationResolver} 2126 * changed (among other places). 2127 */ updateRemoteFieldClassificationService()2128 void updateRemoteFieldClassificationService() { 2129 synchronized (mLock) { 2130 if (mRemoteFieldClassificationService != null) { 2131 if (sVerbose) { 2132 Slog.v(TAG, "updateRemoteFieldClassificationService(): " 2133 + "destroying old remote service"); 2134 } 2135 mRemoteFieldClassificationService.unbind(); 2136 mRemoteFieldClassificationService = null; 2137 mRemoteFieldClassificationServiceInfo = null; 2138 } 2139 2140 final boolean available = isFieldClassificationServiceAvailableLocked(); 2141 if (sVerbose) Slog.v(TAG, "updateRemoteFieldClassificationService(): " + available); 2142 2143 if (available) { 2144 mRemoteFieldClassificationService = getRemoteFieldClassificationServiceLocked(); 2145 } 2146 } 2147 } 2148 isFieldClassificationServiceAvailableLocked()2149 private boolean isFieldClassificationServiceAvailableLocked() { 2150 if (mMaster.verbose) { 2151 Slog.v(TAG, "isFieldClassificationService(): " 2152 + "setupCompleted=" + isSetupCompletedLocked() 2153 + ", disabled=" + isDisabledByUserRestrictionsLocked() 2154 + ", augmentedService=" 2155 + mMaster.mFieldClassificationResolver.getServiceName(mUserId)); 2156 } 2157 if (!isSetupCompletedLocked() || isDisabledByUserRestrictionsLocked() 2158 || mMaster.mFieldClassificationResolver.getServiceName(mUserId) == null) { 2159 return false; 2160 } 2161 return true; 2162 } 2163 isRemoteClassificationServiceForUserLocked(int callingUid)2164 boolean isRemoteClassificationServiceForUserLocked(int callingUid) { 2165 return mRemoteFieldClassificationServiceInfo != null 2166 && mRemoteFieldClassificationServiceInfo.applicationInfo.uid == callingUid; 2167 } 2168 2169 @Override toString()2170 public String toString() { 2171 return "AutofillManagerServiceImpl: [userId=" + mUserId 2172 + ", component=" + (mInfo != null 2173 ? mInfo.getServiceInfo().getComponentName() : null) + "]"; 2174 } 2175 2176 /** Task used to prune abandoned session */ 2177 private class PruneTask extends AsyncTask<Void, Void, Void> { 2178 @Override doInBackground(Void... ignored)2179 protected Void doInBackground(Void... ignored) { 2180 int numSessionsToRemove; 2181 2182 SparseArray<IBinder> sessionsToRemove; 2183 2184 synchronized (mLock) { 2185 numSessionsToRemove = mSessions.size(); 2186 sessionsToRemove = new SparseArray<>(numSessionsToRemove); 2187 2188 for (int i = 0; i < numSessionsToRemove; i++) { 2189 Session session = mSessions.valueAt(i); 2190 2191 sessionsToRemove.put(session.id, session.getActivityTokenLocked()); 2192 } 2193 } 2194 2195 final ActivityTaskManagerInternal atmInternal = LocalServices.getService( 2196 ActivityTaskManagerInternal.class); 2197 2198 // Only remove sessions which's activities are not known to the activity manager anymore 2199 for (int i = 0; i < numSessionsToRemove; i++) { 2200 // The activity task manager cannot resolve activities that have been removed. 2201 if (atmInternal.getActivityName(sessionsToRemove.valueAt(i)) != null) { 2202 sessionsToRemove.removeAt(i); 2203 i--; 2204 numSessionsToRemove--; 2205 } 2206 } 2207 2208 synchronized (mLock) { 2209 for (int i = 0; i < numSessionsToRemove; i++) { 2210 Session sessionToRemove = mSessions.get(sessionsToRemove.keyAt(i)); 2211 2212 if (sessionToRemove != null && sessionsToRemove.valueAt(i) 2213 == sessionToRemove.getActivityTokenLocked()) { 2214 if (sessionToRemove.isSaveUiShowingLocked()) { 2215 if (sVerbose) { 2216 Slog.v(TAG, "Session " + sessionToRemove.id + " is saving"); 2217 } 2218 } else { 2219 if (sDebug) { 2220 Slog.i(TAG, "Prune session " + sessionToRemove.id + " (" 2221 + sessionToRemove.getActivityTokenLocked() + ")"); 2222 } 2223 sessionToRemove.removeFromServiceLocked(); 2224 } 2225 } 2226 } 2227 } 2228 2229 return null; 2230 } 2231 } 2232 } 2233