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.BaseBundle; 34 import android.os.Build; 35 import android.os.Bundle; 36 import android.os.CancellationSignal; 37 import android.os.Handler; 38 import android.os.IBinder; 39 import android.os.ICancellationSignal; 40 import android.os.Looper; 41 import android.os.RemoteException; 42 import android.os.SystemClock; 43 import android.service.autofill.Dataset; 44 import android.service.autofill.FillEventHistory; 45 import android.service.autofill.augmented.PresentationParams.SystemPopupPresentationParams; 46 import android.util.Log; 47 import android.util.Pair; 48 import android.util.SparseArray; 49 import android.view.autofill.AutofillId; 50 import android.view.autofill.AutofillManager; 51 import android.view.autofill.AutofillValue; 52 import android.view.autofill.IAugmentedAutofillManagerClient; 53 import android.view.autofill.IAutofillWindowPresenter; 54 import android.view.inputmethod.InlineSuggestionsRequest; 55 56 import com.android.internal.annotations.GuardedBy; 57 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 58 59 import java.io.FileDescriptor; 60 import java.io.PrintWriter; 61 import java.lang.annotation.Retention; 62 import java.lang.annotation.RetentionPolicy; 63 import java.util.ArrayList; 64 import java.util.List; 65 66 /** 67 * A service used to augment the Autofill subsystem by potentially providing autofill data when the 68 * "standard" workflow failed (for example, because the standard AutofillService didn't have data). 69 * 70 * @hide 71 */ 72 @SystemApi 73 @TestApi 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 blacklisted 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 335 /** @hide */ dump(FileDescriptor fd, PrintWriter pw, String[] args)336 protected final void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 337 pw.print("Service component: "); pw.println( 338 ComponentName.flattenToShortString(mServiceComponentName)); 339 if (mAutofillProxies != null) { 340 final int size = mAutofillProxies.size(); 341 pw.print("Number proxies: "); pw.println(size); 342 for (int i = 0; i < size; i++) { 343 final int sessionId = mAutofillProxies.keyAt(i); 344 final AutofillProxy proxy = mAutofillProxies.valueAt(i); 345 pw.print(i); pw.print(") SessionId="); pw.print(sessionId); pw.println(":"); 346 proxy.dump(" ", pw); 347 } 348 } 349 dump(pw, args); 350 } 351 352 /** 353 * Implementation specific {@code dump}. The child class can override the method to provide 354 * additional information about the Service's state into the dumpsys output. 355 * 356 * @param pw The PrintWriter to which you should dump your state. This will be closed for 357 * you after you return. 358 * @param args additional arguments to the dump request. 359 */ dump(@onNull PrintWriter pw, @SuppressWarnings("unused") @NonNull String[] args)360 protected void dump(@NonNull PrintWriter pw, 361 @SuppressWarnings("unused") @NonNull String[] args) { 362 pw.print(getClass().getName()); pw.println(": nothing to dump"); 363 } 364 365 /** 366 * Gets the inline augmented autofill events that happened after the last 367 * {@link #onFillRequest(FillRequest, CancellationSignal, FillController, FillCallback)} call. 368 * 369 * <p>The history is not persisted over reboots, and it's cleared every time the service 370 * replies to a 371 * {@link #onFillRequest(FillRequest, CancellationSignal, FillController, FillCallback)} 372 * by calling {@link FillCallback#onSuccess(FillResponse)}. Hence, the service should call 373 * {@link #getFillEventHistory() before finishing the {@link FillCallback}. 374 * 375 * <p>Also note that the events from the dropdown suggestion UI is not stored in the history 376 * since the service owns the UI. 377 * 378 * @return The history or {@code null} if there are no events. 379 */ getFillEventHistory()380 @Nullable public final FillEventHistory getFillEventHistory() { 381 final AutofillManager afm = getSystemService(AutofillManager.class); 382 383 if (afm == null) { 384 return null; 385 } else { 386 return afm.getFillEventHistory(); 387 } 388 } 389 390 /** @hide */ 391 static final class AutofillProxy { 392 393 static final int REPORT_EVENT_NO_RESPONSE = 1; 394 static final int REPORT_EVENT_UI_SHOWN = 2; 395 static final int REPORT_EVENT_UI_DESTROYED = 3; 396 static final int REPORT_EVENT_INLINE_RESPONSE = 4; 397 398 @IntDef(prefix = { "REPORT_EVENT_" }, value = { 399 REPORT_EVENT_NO_RESPONSE, 400 REPORT_EVENT_UI_SHOWN, 401 REPORT_EVENT_UI_DESTROYED, 402 REPORT_EVENT_INLINE_RESPONSE 403 }) 404 @Retention(RetentionPolicy.SOURCE) 405 @interface ReportEvent{} 406 407 408 private final Object mLock = new Object(); 409 private final IAugmentedAutofillManagerClient mClient; 410 private final int mSessionId; 411 public final int mTaskId; 412 public final ComponentName mComponentName; 413 // Used for metrics / debug only 414 private String mServicePackageName; 415 @GuardedBy("mLock") 416 private AutofillId mFocusedId; 417 @GuardedBy("mLock") 418 private AutofillValue mFocusedValue; 419 @GuardedBy("mLock") 420 private IFillCallback mCallback; 421 422 /** 423 * Id of the last field that cause the Autofill UI to be shown. 424 * 425 * <p>Used to make sure the SmartSuggestionsParams is updated when a new fields is focused. 426 */ 427 @GuardedBy("mLock") 428 private AutofillId mLastShownId; 429 430 // Objects used to log metrics 431 private final long mFirstRequestTime; 432 private long mFirstOnSuccessTime; 433 private long mUiFirstShownTime; 434 private long mUiFirstDestroyedTime; 435 436 @GuardedBy("mLock") 437 private SystemPopupPresentationParams mSmartSuggestion; 438 439 @GuardedBy("mLock") 440 private FillWindow mFillWindow; 441 442 private CancellationSignal mCancellationSignal; 443 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)444 private AutofillProxy(int sessionId, @NonNull IBinder client, int taskId, 445 @NonNull ComponentName serviceComponentName, 446 @NonNull ComponentName componentName, @NonNull AutofillId focusedId, 447 @Nullable AutofillValue focusedValue, long requestTime, 448 @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal) { 449 mSessionId = sessionId; 450 mClient = IAugmentedAutofillManagerClient.Stub.asInterface(client); 451 mCallback = callback; 452 mTaskId = taskId; 453 mComponentName = componentName; 454 mServicePackageName = serviceComponentName.getPackageName(); 455 mFocusedId = focusedId; 456 mFocusedValue = focusedValue; 457 mFirstRequestTime = requestTime; 458 mCancellationSignal = cancellationSignal; 459 // TODO(b/123099468): linkToDeath 460 } 461 462 @NonNull getSmartSuggestionParams()463 public SystemPopupPresentationParams getSmartSuggestionParams() { 464 synchronized (mLock) { 465 if (mSmartSuggestion != null && mFocusedId.equals(mLastShownId)) { 466 return mSmartSuggestion; 467 } 468 Rect rect; 469 try { 470 rect = mClient.getViewCoordinates(mFocusedId); 471 } catch (RemoteException e) { 472 Log.w(TAG, "Could not get coordinates for " + mFocusedId); 473 return null; 474 } 475 if (rect == null) { 476 if (sDebug) Log.d(TAG, "getViewCoordinates(" + mFocusedId + ") returned null"); 477 return null; 478 } 479 mSmartSuggestion = new SystemPopupPresentationParams(this, rect); 480 mLastShownId = mFocusedId; 481 return mSmartSuggestion; 482 } 483 } 484 autofill(@onNull List<Pair<AutofillId, AutofillValue>> pairs)485 public void autofill(@NonNull List<Pair<AutofillId, AutofillValue>> pairs) 486 throws RemoteException { 487 final int size = pairs.size(); 488 final List<AutofillId> ids = new ArrayList<>(size); 489 final List<AutofillValue> values = new ArrayList<>(size); 490 for (int i = 0; i < size; i++) { 491 final Pair<AutofillId, AutofillValue> pair = pairs.get(i); 492 ids.add(pair.first); 493 values.add(pair.second); 494 } 495 final boolean hideHighlight = size == 1 && ids.get(0).equals(mFocusedId); 496 mClient.autofill(mSessionId, ids, values, hideHighlight); 497 } 498 setFillWindow(@onNull FillWindow fillWindow)499 public void setFillWindow(@NonNull FillWindow fillWindow) { 500 synchronized (mLock) { 501 mFillWindow = fillWindow; 502 } 503 } 504 getFillWindow()505 public FillWindow getFillWindow() { 506 synchronized (mLock) { 507 return mFillWindow; 508 } 509 } 510 requestShowFillUi(int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter)511 public void requestShowFillUi(int width, int height, Rect anchorBounds, 512 IAutofillWindowPresenter presenter) throws RemoteException { 513 if (mCancellationSignal.isCanceled()) { 514 if (sVerbose) { 515 Log.v(TAG, "requestShowFillUi() not showing because request is cancelled"); 516 } 517 return; 518 } 519 mClient.requestShowFillUi(mSessionId, mFocusedId, width, height, anchorBounds, 520 presenter); 521 } 522 requestHideFillUi()523 public void requestHideFillUi() throws RemoteException { 524 mClient.requestHideFillUi(mSessionId, mFocusedId); 525 } 526 527 requestAutofill()528 private boolean requestAutofill() throws RemoteException { 529 return mClient.requestAutofill(mSessionId, mFocusedId); 530 } 531 update(@onNull AutofillId focusedId, @NonNull AutofillValue focusedValue, @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal)532 private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue, 533 @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal) { 534 synchronized (mLock) { 535 mFocusedId = focusedId; 536 mFocusedValue = focusedValue; 537 if (mCallback != null) { 538 try { 539 if (!mCallback.isCompleted()) { 540 mCallback.cancel(); 541 } 542 } catch (RemoteException e) { 543 Log.e(TAG, "failed to check current pending request status", e); 544 } 545 Log.d(TAG, "mCallback is updated."); 546 } 547 mCallback = callback; 548 mCancellationSignal = cancellationSignal; 549 } 550 } 551 552 @NonNull getFocusedId()553 public AutofillId getFocusedId() { 554 synchronized (mLock) { 555 return mFocusedId; 556 } 557 } 558 559 @NonNull getFocusedValue()560 public AutofillValue getFocusedValue() { 561 synchronized (mLock) { 562 return mFocusedValue; 563 } 564 } 565 reportResult(@ullable List<Dataset> inlineSuggestionsData, @Nullable Bundle clientState, boolean showingFillWindow)566 void reportResult(@Nullable List<Dataset> inlineSuggestionsData, 567 @Nullable Bundle clientState, boolean showingFillWindow) { 568 try { 569 mCallback.onSuccess(inlineSuggestionsData, clientState, showingFillWindow); 570 } catch (RemoteException e) { 571 Log.e(TAG, "Error calling back with the inline suggestions data: " + e); 572 } 573 } 574 logEvent(@eportEvent int event)575 void logEvent(@ReportEvent int event) { 576 if (sVerbose) Log.v(TAG, "returnAndLogResult(): " + event); 577 long duration = -1; 578 int type = MetricsEvent.TYPE_UNKNOWN; 579 580 switch (event) { 581 case REPORT_EVENT_NO_RESPONSE: { 582 type = MetricsEvent.TYPE_SUCCESS; 583 if (mFirstOnSuccessTime == 0) { 584 mFirstOnSuccessTime = SystemClock.elapsedRealtime(); 585 duration = mFirstOnSuccessTime - mFirstRequestTime; 586 if (sDebug) { 587 Log.d(TAG, "Service responded nothing in " + formatDuration(duration)); 588 } 589 } 590 } break; 591 592 case REPORT_EVENT_INLINE_RESPONSE: { 593 // TODO: Define a constant and log this event 594 // type = MetricsEvent.TYPE_SUCCESS_INLINE; 595 if (mFirstOnSuccessTime == 0) { 596 mFirstOnSuccessTime = SystemClock.elapsedRealtime(); 597 duration = mFirstOnSuccessTime - mFirstRequestTime; 598 if (sDebug) { 599 Log.d(TAG, "Service responded nothing in " + formatDuration(duration)); 600 } 601 } 602 } break; 603 604 case REPORT_EVENT_UI_SHOWN: { 605 type = MetricsEvent.TYPE_OPEN; 606 if (mUiFirstShownTime == 0) { 607 mUiFirstShownTime = SystemClock.elapsedRealtime(); 608 duration = mUiFirstShownTime - mFirstRequestTime; 609 if (sDebug) Log.d(TAG, "UI shown in " + formatDuration(duration)); 610 } 611 } break; 612 613 case REPORT_EVENT_UI_DESTROYED: { 614 type = MetricsEvent.TYPE_CLOSE; 615 if (mUiFirstDestroyedTime == 0) { 616 mUiFirstDestroyedTime = SystemClock.elapsedRealtime(); 617 duration = mUiFirstDestroyedTime - mFirstRequestTime; 618 if (sDebug) Log.d(TAG, "UI destroyed in " + formatDuration(duration)); 619 } 620 } break; 621 622 default: 623 Log.w(TAG, "invalid event reported: " + event); 624 } 625 logResponse(type, mServicePackageName, mComponentName, mSessionId, duration); 626 } 627 dump(@onNull String prefix, @NonNull PrintWriter pw)628 public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { 629 pw.print(prefix); pw.print("sessionId: "); pw.println(mSessionId); 630 pw.print(prefix); pw.print("taskId: "); pw.println(mTaskId); 631 pw.print(prefix); pw.print("component: "); 632 pw.println(mComponentName.flattenToShortString()); 633 pw.print(prefix); pw.print("focusedId: "); pw.println(mFocusedId); 634 if (mFocusedValue != null) { 635 pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue); 636 } 637 if (mLastShownId != null) { 638 pw.print(prefix); pw.print("lastShownId: "); pw.println(mLastShownId); 639 } 640 pw.print(prefix); pw.print("client: "); pw.println(mClient); 641 final String prefix2 = prefix + " "; 642 if (mFillWindow != null) { 643 pw.print(prefix); pw.println("window:"); 644 mFillWindow.dump(prefix2, pw); 645 } 646 if (mSmartSuggestion != null) { 647 pw.print(prefix); pw.println("smartSuggestion:"); 648 mSmartSuggestion.dump(prefix2, pw); 649 } 650 if (mFirstOnSuccessTime > 0) { 651 final long responseTime = mFirstOnSuccessTime - mFirstRequestTime; 652 pw.print(prefix); pw.print("response time: "); 653 formatDuration(responseTime, pw); pw.println(); 654 } 655 656 if (mUiFirstShownTime > 0) { 657 final long uiRenderingTime = mUiFirstShownTime - mFirstRequestTime; 658 pw.print(prefix); pw.print("UI rendering time: "); 659 formatDuration(uiRenderingTime, pw); pw.println(); 660 } 661 662 if (mUiFirstDestroyedTime > 0) { 663 final long uiTotalTime = mUiFirstDestroyedTime - mFirstRequestTime; 664 pw.print(prefix); pw.print("UI life time: "); 665 formatDuration(uiTotalTime, pw); pw.println(); 666 } 667 } 668 destroy()669 private void destroy() { 670 synchronized (mLock) { 671 if (mFillWindow != null) { 672 if (sDebug) Log.d(TAG, "destroying window"); 673 mFillWindow.destroy(); 674 mFillWindow = null; 675 } 676 } 677 } 678 } 679 } 680