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