1 /* 2 * Copyright (C) 2018 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 package android.service.autofill.augmented; 17 18 import static android.service.autofill.augmented.Helper.logResponse; 19 import static android.util.TimeUtils.formatDuration; 20 21 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 22 23 import android.annotation.CallSuper; 24 import android.annotation.IntDef; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.SystemApi; 28 import android.annotation.TestApi; 29 import android.app.Service; 30 import android.content.ComponentName; 31 import android.content.Intent; 32 import android.graphics.Rect; 33 import android.os.Build; 34 import android.os.CancellationSignal; 35 import android.os.Handler; 36 import android.os.IBinder; 37 import android.os.ICancellationSignal; 38 import android.os.Looper; 39 import android.os.RemoteException; 40 import android.os.SystemClock; 41 import android.service.autofill.augmented.PresentationParams.SystemPopupPresentationParams; 42 import android.util.Log; 43 import android.util.Pair; 44 import android.util.SparseArray; 45 import android.view.autofill.AutofillId; 46 import android.view.autofill.AutofillManager; 47 import android.view.autofill.AutofillValue; 48 import android.view.autofill.IAugmentedAutofillManagerClient; 49 import android.view.autofill.IAutofillWindowPresenter; 50 51 import com.android.internal.annotations.GuardedBy; 52 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 53 54 import java.io.FileDescriptor; 55 import java.io.PrintWriter; 56 import java.lang.annotation.Retention; 57 import java.lang.annotation.RetentionPolicy; 58 import java.util.ArrayList; 59 import java.util.List; 60 61 /** 62 * A service used to augment the Autofill subsystem by potentially providing autofill data when the 63 * "standard" workflow failed (for example, because the standard AutofillService didn't have data). 64 * 65 * @hide 66 */ 67 @SystemApi 68 @TestApi 69 public abstract class AugmentedAutofillService extends Service { 70 71 private static final String TAG = AugmentedAutofillService.class.getSimpleName(); 72 73 static boolean sDebug = Build.IS_USER ? false : true; 74 static boolean sVerbose = false; 75 76 /** 77 * The {@link Intent} that must be declared as handled by the service. 78 * To be supported, the service must also require the 79 * {@link android.Manifest.permission#BIND_AUGMENTED_AUTOFILL_SERVICE} permission so 80 * that other applications can not abuse it. 81 */ 82 public static final String SERVICE_INTERFACE = 83 "android.service.autofill.augmented.AugmentedAutofillService"; 84 85 private Handler mHandler; 86 87 private SparseArray<AutofillProxy> mAutofillProxies; 88 89 // Used for metrics / debug only 90 private ComponentName mServiceComponentName; 91 92 private final IAugmentedAutofillService mInterface = new IAugmentedAutofillService.Stub() { 93 94 @Override 95 public void onConnected(boolean debug, boolean verbose) { 96 mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnConnected, 97 AugmentedAutofillService.this, debug, verbose)); 98 } 99 100 @Override 101 public void onDisconnected() { 102 mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnDisconnected, 103 AugmentedAutofillService.this)); 104 } 105 106 @Override 107 public void onFillRequest(int sessionId, IBinder client, int taskId, 108 ComponentName componentName, AutofillId focusedId, AutofillValue focusedValue, 109 long requestTime, IFillCallback callback) { 110 mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnFillRequest, 111 AugmentedAutofillService.this, sessionId, client, taskId, componentName, 112 focusedId, focusedValue, requestTime, callback)); 113 } 114 115 @Override 116 public void onDestroyAllFillWindowsRequest() { 117 mHandler.sendMessage( 118 obtainMessage(AugmentedAutofillService::handleOnDestroyAllFillWindowsRequest, 119 AugmentedAutofillService.this)); 120 } 121 }; 122 123 @CallSuper 124 @Override onCreate()125 public void onCreate() { 126 super.onCreate(); 127 mHandler = new Handler(Looper.getMainLooper(), null, true); 128 } 129 130 /** @hide */ 131 @Override onBind(Intent intent)132 public final IBinder onBind(Intent intent) { 133 mServiceComponentName = intent.getComponent(); 134 if (SERVICE_INTERFACE.equals(intent.getAction())) { 135 return mInterface.asBinder(); 136 } 137 Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent); 138 return null; 139 } 140 141 @Override onUnbind(Intent intent)142 public boolean onUnbind(Intent intent) { 143 mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnUnbind, 144 AugmentedAutofillService.this)); 145 return false; 146 } 147 148 /** 149 * Called when the Android system connects to service. 150 * 151 * <p>You should generally do initialization here rather than in {@link #onCreate}. 152 */ onConnected()153 public void onConnected() { 154 } 155 156 /** 157 * Asks the service to handle an "augmented" autofill request. 158 * 159 * <p>This method is called when the "stantard" autofill service cannot handle a request, which 160 * typically occurs when: 161 * <ul> 162 * <li>Service does not recognize what should be autofilled. 163 * <li>Service does not have data to fill the request. 164 * <li>Service blacklisted that app (or activity) for autofill. 165 * <li>App disabled itself for autofill. 166 * </ul> 167 * 168 * <p>Differently from the standard autofill workflow, on augmented autofill the service is 169 * responsible to generate the autofill UI and request the Android system to autofill the 170 * activity when the user taps an action in that UI (through the 171 * {@link FillController#autofill(List)} method). 172 * 173 * <p>The service <b>MUST</b> call {@link 174 * FillCallback#onSuccess(android.service.autofill.augmented.FillResponse)} as soon as possible, 175 * passing {@code null} when it cannot fulfill the request. 176 * @param request the request to handle. 177 * @param cancellationSignal signal for observing cancellation requests. The system will use 178 * this to notify you that the fill result is no longer needed and you should stop 179 * handling this fill request in order to save resources. 180 * @param controller object used to interact with the autofill system. 181 * @param callback object used to notify the result of the request. Service <b>must</b> call 182 * {@link FillCallback#onSuccess(android.service.autofill.augmented.FillResponse)}. 183 */ onFillRequest(@onNull FillRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull FillController controller, @NonNull FillCallback callback)184 public void onFillRequest(@NonNull FillRequest request, 185 @NonNull CancellationSignal cancellationSignal, @NonNull FillController controller, 186 @NonNull FillCallback callback) { 187 } 188 189 /** 190 * Called when the Android system disconnects from the service. 191 * 192 * <p> At this point this service may no longer be an active {@link AugmentedAutofillService}. 193 * It should not make calls on {@link AutofillManager} that requires the caller to be 194 * the current service. 195 */ onDisconnected()196 public void onDisconnected() { 197 } 198 handleOnConnected(boolean debug, boolean verbose)199 private void handleOnConnected(boolean debug, boolean verbose) { 200 if (sDebug || debug) { 201 Log.d(TAG, "handleOnConnected(): debug=" + debug + ", verbose=" + verbose); 202 } 203 sDebug = debug; 204 sVerbose = verbose; 205 onConnected(); 206 } 207 handleOnDisconnected()208 private void handleOnDisconnected() { 209 onDisconnected(); 210 } 211 handleOnFillRequest(int sessionId, @NonNull IBinder client, int taskId, @NonNull ComponentName componentName, @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue, long requestTime, @NonNull IFillCallback callback)212 private void handleOnFillRequest(int sessionId, @NonNull IBinder client, int taskId, 213 @NonNull ComponentName componentName, @NonNull AutofillId focusedId, 214 @Nullable AutofillValue focusedValue, long requestTime, 215 @NonNull IFillCallback callback) { 216 if (mAutofillProxies == null) { 217 mAutofillProxies = new SparseArray<>(); 218 } 219 220 final ICancellationSignal transport = CancellationSignal.createTransport(); 221 final CancellationSignal cancellationSignal = CancellationSignal.fromTransport(transport); 222 AutofillProxy proxy = mAutofillProxies.get(sessionId); 223 if (proxy == null) { 224 proxy = new AutofillProxy(sessionId, client, taskId, mServiceComponentName, 225 componentName, focusedId, focusedValue, requestTime, callback, 226 cancellationSignal); 227 mAutofillProxies.put(sessionId, proxy); 228 } else { 229 // TODO(b/123099468): figure out if it's ok to reuse the proxy; add logging 230 if (sDebug) Log.d(TAG, "Reusing proxy for session " + sessionId); 231 proxy.update(focusedId, focusedValue, callback, cancellationSignal); 232 } 233 234 try { 235 callback.onCancellable(transport); 236 } catch (RemoteException e) { 237 e.rethrowFromSystemServer(); 238 } 239 240 onFillRequest(new FillRequest(proxy), cancellationSignal, new FillController(proxy), 241 new FillCallback(proxy)); 242 } 243 handleOnDestroyAllFillWindowsRequest()244 private void handleOnDestroyAllFillWindowsRequest() { 245 if (mAutofillProxies != null) { 246 final int size = mAutofillProxies.size(); 247 for (int i = 0; i < size; i++) { 248 final int sessionId = mAutofillProxies.keyAt(i); 249 final AutofillProxy proxy = mAutofillProxies.valueAt(i); 250 if (proxy == null) { 251 // TODO(b/123100811): this might be fine, in which case we should logv it 252 Log.w(TAG, "No proxy for session " + sessionId); 253 return; 254 } 255 if (proxy.mCallback != null) { 256 try { 257 if (!proxy.mCallback.isCompleted()) { 258 proxy.mCallback.cancel(); 259 } 260 } catch (Exception e) { 261 Log.e(TAG, "failed to check current pending request status", e); 262 } 263 } 264 proxy.destroy(); 265 } 266 mAutofillProxies.clear(); 267 } 268 } 269 handleOnUnbind()270 private void handleOnUnbind() { 271 if (mAutofillProxies == null) { 272 if (sDebug) Log.d(TAG, "onUnbind(): no proxy to destroy"); 273 return; 274 } 275 final int size = mAutofillProxies.size(); 276 if (sDebug) Log.d(TAG, "onUnbind(): destroying " + size + " proxies"); 277 for (int i = 0; i < size; i++) { 278 final AutofillProxy proxy = mAutofillProxies.valueAt(i); 279 try { 280 proxy.destroy(); 281 } catch (Exception e) { 282 Log.w(TAG, "error destroying " + proxy); 283 } 284 } 285 mAutofillProxies = null; 286 } 287 288 @Override 289 /** @hide */ dump(FileDescriptor fd, PrintWriter pw, String[] args)290 protected final void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 291 pw.print("Service component: "); pw.println( 292 ComponentName.flattenToShortString(mServiceComponentName)); 293 if (mAutofillProxies != null) { 294 final int size = mAutofillProxies.size(); 295 pw.print("Number proxies: "); pw.println(size); 296 for (int i = 0; i < size; i++) { 297 final int sessionId = mAutofillProxies.keyAt(i); 298 final AutofillProxy proxy = mAutofillProxies.valueAt(i); 299 pw.print(i); pw.print(") SessionId="); pw.print(sessionId); pw.println(":"); 300 proxy.dump(" ", pw); 301 } 302 } 303 dump(pw, args); 304 } 305 306 /** 307 * Implementation specific {@code dump}. The child class can override the method to provide 308 * additional information about the Service's state into the dumpsys output. 309 * 310 * @param pw The PrintWriter to which you should dump your state. This will be closed for 311 * you after you return. 312 * @param args additional arguments to the dump request. 313 */ dump(@onNull PrintWriter pw, @SuppressWarnings("unused") @NonNull String[] args)314 protected void dump(@NonNull PrintWriter pw, 315 @SuppressWarnings("unused") @NonNull String[] args) { 316 pw.print(getClass().getName()); pw.println(": nothing to dump"); 317 } 318 319 /** @hide */ 320 static final class AutofillProxy { 321 322 static final int REPORT_EVENT_NO_RESPONSE = 1; 323 static final int REPORT_EVENT_UI_SHOWN = 2; 324 static final int REPORT_EVENT_UI_DESTROYED = 3; 325 326 @IntDef(prefix = { "REPORT_EVENT_" }, value = { 327 REPORT_EVENT_NO_RESPONSE, 328 REPORT_EVENT_UI_SHOWN, 329 REPORT_EVENT_UI_DESTROYED 330 }) 331 @Retention(RetentionPolicy.SOURCE) 332 @interface ReportEvent{} 333 334 335 private final Object mLock = new Object(); 336 private final IAugmentedAutofillManagerClient mClient; 337 private final int mSessionId; 338 public final int taskId; 339 public final ComponentName componentName; 340 // Used for metrics / debug only 341 private String mServicePackageName; 342 @GuardedBy("mLock") 343 private AutofillId mFocusedId; 344 @GuardedBy("mLock") 345 private AutofillValue mFocusedValue; 346 @GuardedBy("mLock") 347 private IFillCallback mCallback; 348 349 /** 350 * Id of the last field that cause the Autofill UI to be shown. 351 * 352 * <p>Used to make sure the SmartSuggestionsParams is updated when a new fields is focused. 353 */ 354 @GuardedBy("mLock") 355 private AutofillId mLastShownId; 356 357 // Objects used to log metrics 358 private final long mFirstRequestTime; 359 private long mFirstOnSuccessTime; 360 private long mUiFirstShownTime; 361 private long mUiFirstDestroyedTime; 362 363 @GuardedBy("mLock") 364 private SystemPopupPresentationParams mSmartSuggestion; 365 366 @GuardedBy("mLock") 367 private FillWindow mFillWindow; 368 369 private CancellationSignal mCancellationSignal; 370 AutofillProxy(int sessionId, @NonNull IBinder client, int taskId, @NonNull ComponentName serviceComponentName, @NonNull ComponentName componentName, @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue, long requestTime, @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal)371 private AutofillProxy(int sessionId, @NonNull IBinder client, int taskId, 372 @NonNull ComponentName serviceComponentName, 373 @NonNull ComponentName componentName, @NonNull AutofillId focusedId, 374 @Nullable AutofillValue focusedValue, long requestTime, 375 @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal) { 376 mSessionId = sessionId; 377 mClient = IAugmentedAutofillManagerClient.Stub.asInterface(client); 378 mCallback = callback; 379 this.taskId = taskId; 380 this.componentName = componentName; 381 mServicePackageName = serviceComponentName.getPackageName(); 382 mFocusedId = focusedId; 383 mFocusedValue = focusedValue; 384 mFirstRequestTime = requestTime; 385 mCancellationSignal = cancellationSignal; 386 // TODO(b/123099468): linkToDeath 387 } 388 389 @NonNull getSmartSuggestionParams()390 public SystemPopupPresentationParams getSmartSuggestionParams() { 391 synchronized (mLock) { 392 if (mSmartSuggestion != null && mFocusedId.equals(mLastShownId)) { 393 return mSmartSuggestion; 394 } 395 Rect rect; 396 try { 397 rect = mClient.getViewCoordinates(mFocusedId); 398 } catch (RemoteException e) { 399 Log.w(TAG, "Could not get coordinates for " + mFocusedId); 400 return null; 401 } 402 if (rect == null) { 403 if (sDebug) Log.d(TAG, "getViewCoordinates(" + mFocusedId + ") returned null"); 404 return null; 405 } 406 mSmartSuggestion = new SystemPopupPresentationParams(this, rect); 407 mLastShownId = mFocusedId; 408 return mSmartSuggestion; 409 } 410 } 411 autofill(@onNull List<Pair<AutofillId, AutofillValue>> pairs)412 public void autofill(@NonNull List<Pair<AutofillId, AutofillValue>> pairs) 413 throws RemoteException { 414 final int size = pairs.size(); 415 final List<AutofillId> ids = new ArrayList<>(size); 416 final List<AutofillValue> values = new ArrayList<>(size); 417 for (int i = 0; i < size; i++) { 418 final Pair<AutofillId, AutofillValue> pair = pairs.get(i); 419 ids.add(pair.first); 420 values.add(pair.second); 421 } 422 mClient.autofill(mSessionId, ids, values); 423 } 424 setFillWindow(@onNull FillWindow fillWindow)425 public void setFillWindow(@NonNull FillWindow fillWindow) { 426 synchronized (mLock) { 427 mFillWindow = fillWindow; 428 } 429 } 430 getFillWindow()431 public FillWindow getFillWindow() { 432 synchronized (mLock) { 433 return mFillWindow; 434 } 435 } 436 requestShowFillUi(int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter)437 public void requestShowFillUi(int width, int height, Rect anchorBounds, 438 IAutofillWindowPresenter presenter) throws RemoteException { 439 if (mCancellationSignal.isCanceled()) { 440 if (sVerbose) { 441 Log.v(TAG, "requestShowFillUi() not showing because request is cancelled"); 442 } 443 return; 444 } 445 mClient.requestShowFillUi(mSessionId, mFocusedId, width, height, anchorBounds, 446 presenter); 447 } 448 requestHideFillUi()449 public void requestHideFillUi() throws RemoteException { 450 mClient.requestHideFillUi(mSessionId, mFocusedId); 451 } 452 update(@onNull AutofillId focusedId, @NonNull AutofillValue focusedValue, @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal)453 private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue, 454 @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal) { 455 synchronized (mLock) { 456 mFocusedId = focusedId; 457 mFocusedValue = focusedValue; 458 if (mCallback != null) { 459 try { 460 if (!mCallback.isCompleted()) { 461 mCallback.cancel(); 462 } 463 } catch (RemoteException e) { 464 Log.e(TAG, "failed to check current pending request status", e); 465 } 466 Log.d(TAG, "mCallback is updated."); 467 } 468 mCallback = callback; 469 mCancellationSignal = cancellationSignal; 470 } 471 } 472 473 @NonNull getFocusedId()474 public AutofillId getFocusedId() { 475 synchronized (mLock) { 476 return mFocusedId; 477 } 478 } 479 480 @NonNull getFocusedValue()481 public AutofillValue getFocusedValue() { 482 synchronized (mLock) { 483 return mFocusedValue; 484 } 485 } 486 487 // Used (mostly) for metrics. report(@eportEvent int event)488 public void report(@ReportEvent int event) { 489 if (sVerbose) Log.v(TAG, "report(): " + event); 490 long duration = -1; 491 int type = MetricsEvent.TYPE_UNKNOWN; 492 switch (event) { 493 case REPORT_EVENT_NO_RESPONSE: 494 type = MetricsEvent.TYPE_SUCCESS; 495 if (mFirstOnSuccessTime == 0) { 496 mFirstOnSuccessTime = SystemClock.elapsedRealtime(); 497 duration = mFirstOnSuccessTime - mFirstRequestTime; 498 if (sDebug) { 499 Log.d(TAG, "Service responded nothing in " + formatDuration(duration)); 500 } 501 } 502 try { 503 mCallback.onSuccess(); 504 } catch (RemoteException e) { 505 Log.e(TAG, "Error reporting success: " + e); 506 } 507 break; 508 case REPORT_EVENT_UI_SHOWN: 509 type = MetricsEvent.TYPE_OPEN; 510 if (mUiFirstShownTime == 0) { 511 mUiFirstShownTime = SystemClock.elapsedRealtime(); 512 duration = mUiFirstShownTime - mFirstRequestTime; 513 if (sDebug) Log.d(TAG, "UI shown in " + formatDuration(duration)); 514 } 515 break; 516 case REPORT_EVENT_UI_DESTROYED: 517 type = MetricsEvent.TYPE_CLOSE; 518 if (mUiFirstDestroyedTime == 0) { 519 mUiFirstDestroyedTime = SystemClock.elapsedRealtime(); 520 duration = mUiFirstDestroyedTime - mFirstRequestTime; 521 if (sDebug) Log.d(TAG, "UI destroyed in " + formatDuration(duration)); 522 } 523 break; 524 default: 525 Log.w(TAG, "invalid event reported: " + event); 526 } 527 logResponse(type, mServicePackageName, componentName, mSessionId, duration); 528 } 529 dump(@onNull String prefix, @NonNull PrintWriter pw)530 public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { 531 pw.print(prefix); pw.print("sessionId: "); pw.println(mSessionId); 532 pw.print(prefix); pw.print("taskId: "); pw.println(taskId); 533 pw.print(prefix); pw.print("component: "); 534 pw.println(componentName.flattenToShortString()); 535 pw.print(prefix); pw.print("focusedId: "); pw.println(mFocusedId); 536 if (mFocusedValue != null) { 537 pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue); 538 } 539 if (mLastShownId != null) { 540 pw.print(prefix); pw.print("lastShownId: "); pw.println(mLastShownId); 541 } 542 pw.print(prefix); pw.print("client: "); pw.println(mClient); 543 final String prefix2 = prefix + " "; 544 if (mFillWindow != null) { 545 pw.print(prefix); pw.println("window:"); 546 mFillWindow.dump(prefix2, pw); 547 } 548 if (mSmartSuggestion != null) { 549 pw.print(prefix); pw.println("smartSuggestion:"); 550 mSmartSuggestion.dump(prefix2, pw); 551 } 552 if (mFirstOnSuccessTime > 0) { 553 final long responseTime = mFirstOnSuccessTime - mFirstRequestTime; 554 pw.print(prefix); pw.print("response time: "); 555 formatDuration(responseTime, pw); pw.println(); 556 } 557 558 if (mUiFirstShownTime > 0) { 559 final long uiRenderingTime = mUiFirstShownTime - mFirstRequestTime; 560 pw.print(prefix); pw.print("UI rendering time: "); 561 formatDuration(uiRenderingTime, pw); pw.println(); 562 } 563 564 if (mUiFirstDestroyedTime > 0) { 565 final long uiTotalTime = mUiFirstDestroyedTime - mFirstRequestTime; 566 pw.print(prefix); pw.print("UI life time: "); 567 formatDuration(uiTotalTime, pw); pw.println(); 568 } 569 } 570 destroy()571 private void destroy() { 572 synchronized (mLock) { 573 if (mFillWindow != null) { 574 if (sDebug) Log.d(TAG, "destroying window"); 575 mFillWindow.destroy(); 576 mFillWindow = null; 577 } 578 } 579 } 580 } 581 } 582