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 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(R) @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 ViewNode mFocusedViewNode; 421 @GuardedBy("mLock") 422 private IFillCallback mCallback; 423 424 /** 425 * Id of the last field that cause the Autofill UI to be shown. 426 * 427 * <p>Used to make sure the SmartSuggestionsParams is updated when a new fields is focused. 428 */ 429 @GuardedBy("mLock") 430 private AutofillId mLastShownId; 431 432 // Objects used to log metrics 433 private final long mFirstRequestTime; 434 private long mFirstOnSuccessTime; 435 private long mUiFirstShownTime; 436 private long mUiFirstDestroyedTime; 437 438 @GuardedBy("mLock") 439 private SystemPopupPresentationParams mSmartSuggestion; 440 441 @GuardedBy("mLock") 442 private FillWindow mFillWindow; 443 444 private CancellationSignal mCancellationSignal; 445 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)446 private AutofillProxy(int sessionId, @NonNull IBinder client, int taskId, 447 @NonNull ComponentName serviceComponentName, 448 @NonNull ComponentName componentName, @NonNull AutofillId focusedId, 449 @Nullable AutofillValue focusedValue, long requestTime, 450 @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal) { 451 mSessionId = sessionId; 452 mClient = IAugmentedAutofillManagerClient.Stub.asInterface(client); 453 mCallback = callback; 454 mTaskId = taskId; 455 mComponentName = componentName; 456 mServicePackageName = serviceComponentName.getPackageName(); 457 mFocusedId = focusedId; 458 mFocusedValue = focusedValue; 459 mFirstRequestTime = requestTime; 460 mCancellationSignal = cancellationSignal; 461 // TODO(b/123099468): linkToDeath 462 } 463 464 @NonNull getSmartSuggestionParams()465 public SystemPopupPresentationParams getSmartSuggestionParams() { 466 synchronized (mLock) { 467 if (mSmartSuggestion != null && mFocusedId.equals(mLastShownId)) { 468 return mSmartSuggestion; 469 } 470 Rect rect; 471 try { 472 rect = mClient.getViewCoordinates(mFocusedId); 473 } catch (RemoteException e) { 474 Log.w(TAG, "Could not get coordinates for " + mFocusedId); 475 return null; 476 } 477 if (rect == null) { 478 if (sDebug) Log.d(TAG, "getViewCoordinates(" + mFocusedId + ") returned null"); 479 return null; 480 } 481 mSmartSuggestion = new SystemPopupPresentationParams(this, rect); 482 mLastShownId = mFocusedId; 483 return mSmartSuggestion; 484 } 485 } 486 autofill(@onNull List<Pair<AutofillId, AutofillValue>> pairs)487 public void autofill(@NonNull List<Pair<AutofillId, AutofillValue>> pairs) 488 throws RemoteException { 489 final int size = pairs.size(); 490 final List<AutofillId> ids = new ArrayList<>(size); 491 final List<AutofillValue> values = new ArrayList<>(size); 492 for (int i = 0; i < size; i++) { 493 final Pair<AutofillId, AutofillValue> pair = pairs.get(i); 494 ids.add(pair.first); 495 values.add(pair.second); 496 } 497 final boolean hideHighlight = size == 1 && ids.get(0).equals(mFocusedId); 498 mClient.autofill(mSessionId, ids, values, hideHighlight); 499 } 500 setFillWindow(@onNull FillWindow fillWindow)501 public void setFillWindow(@NonNull FillWindow fillWindow) { 502 synchronized (mLock) { 503 mFillWindow = fillWindow; 504 } 505 } 506 getFillWindow()507 public FillWindow getFillWindow() { 508 synchronized (mLock) { 509 return mFillWindow; 510 } 511 } 512 requestShowFillUi(int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter)513 public void requestShowFillUi(int width, int height, Rect anchorBounds, 514 IAutofillWindowPresenter presenter) throws RemoteException { 515 if (mCancellationSignal.isCanceled()) { 516 if (sVerbose) { 517 Log.v(TAG, "requestShowFillUi() not showing because request is cancelled"); 518 } 519 return; 520 } 521 mClient.requestShowFillUi(mSessionId, mFocusedId, width, height, anchorBounds, 522 presenter); 523 } 524 requestHideFillUi()525 public void requestHideFillUi() throws RemoteException { 526 mClient.requestHideFillUi(mSessionId, mFocusedId); 527 } 528 529 requestAutofill()530 private boolean requestAutofill() throws RemoteException { 531 return mClient.requestAutofill(mSessionId, mFocusedId); 532 } 533 update(@onNull AutofillId focusedId, @NonNull AutofillValue focusedValue, @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal)534 private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue, 535 @NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal) { 536 synchronized (mLock) { 537 mFocusedId = focusedId; 538 mFocusedValue = focusedValue; 539 mFocusedViewNode = null; 540 if (mCallback != null) { 541 try { 542 if (!mCallback.isCompleted()) { 543 mCallback.cancel(); 544 } 545 } catch (RemoteException e) { 546 Log.e(TAG, "failed to check current pending request status", e); 547 } 548 Log.d(TAG, "mCallback is updated."); 549 } 550 mCallback = callback; 551 mCancellationSignal = cancellationSignal; 552 } 553 } 554 555 @NonNull getFocusedId()556 public AutofillId getFocusedId() { 557 synchronized (mLock) { 558 return mFocusedId; 559 } 560 } 561 562 @NonNull getFocusedValue()563 public AutofillValue getFocusedValue() { 564 synchronized (mLock) { 565 return mFocusedValue; 566 } 567 } 568 reportResult(@ullable List<Dataset> inlineSuggestionsData, @Nullable Bundle clientState, boolean showingFillWindow)569 void reportResult(@Nullable List<Dataset> inlineSuggestionsData, 570 @Nullable Bundle clientState, boolean showingFillWindow) { 571 try { 572 mCallback.onSuccess(inlineSuggestionsData, clientState, showingFillWindow); 573 } catch (RemoteException e) { 574 Log.e(TAG, "Error calling back with the inline suggestions data: " + e); 575 } 576 } 577 578 @Nullable getFocusedViewNode()579 public ViewNode getFocusedViewNode() { 580 synchronized (mLock) { 581 if (mFocusedViewNode == null) { 582 try { 583 final ViewNodeParcelable viewNodeParcelable = mClient.getViewNodeParcelable( 584 mFocusedId); 585 if (viewNodeParcelable != null) { 586 mFocusedViewNode = viewNodeParcelable.getViewNode(); 587 } 588 } catch (RemoteException e) { 589 Log.e(TAG, "Error getting the ViewNode of the focused view: " + e); 590 return null; 591 } 592 } 593 return mFocusedViewNode; 594 } 595 } 596 logEvent(@eportEvent int event)597 void logEvent(@ReportEvent int event) { 598 if (sVerbose) Log.v(TAG, "returnAndLogResult(): " + event); 599 long duration = -1; 600 int type = MetricsEvent.TYPE_UNKNOWN; 601 602 switch (event) { 603 case REPORT_EVENT_NO_RESPONSE: { 604 type = MetricsEvent.TYPE_SUCCESS; 605 if (mFirstOnSuccessTime == 0) { 606 mFirstOnSuccessTime = SystemClock.elapsedRealtime(); 607 duration = mFirstOnSuccessTime - mFirstRequestTime; 608 if (sDebug) { 609 Log.d(TAG, "Service responded nothing in " + formatDuration(duration)); 610 } 611 } 612 } break; 613 614 case REPORT_EVENT_INLINE_RESPONSE: { 615 // TODO: Define a constant and log this event 616 // type = MetricsEvent.TYPE_SUCCESS_INLINE; 617 if (mFirstOnSuccessTime == 0) { 618 mFirstOnSuccessTime = SystemClock.elapsedRealtime(); 619 duration = mFirstOnSuccessTime - mFirstRequestTime; 620 if (sDebug) { 621 Log.d(TAG, "Inline response in " + formatDuration(duration)); 622 } 623 } 624 } break; 625 626 case REPORT_EVENT_UI_SHOWN: { 627 type = MetricsEvent.TYPE_OPEN; 628 if (mUiFirstShownTime == 0) { 629 mUiFirstShownTime = SystemClock.elapsedRealtime(); 630 duration = mUiFirstShownTime - mFirstRequestTime; 631 if (sDebug) Log.d(TAG, "UI shown in " + formatDuration(duration)); 632 } 633 } break; 634 635 case REPORT_EVENT_UI_DESTROYED: { 636 type = MetricsEvent.TYPE_CLOSE; 637 if (mUiFirstDestroyedTime == 0) { 638 mUiFirstDestroyedTime = SystemClock.elapsedRealtime(); 639 duration = mUiFirstDestroyedTime - mFirstRequestTime; 640 if (sDebug) Log.d(TAG, "UI destroyed in " + formatDuration(duration)); 641 } 642 } break; 643 644 default: 645 Log.w(TAG, "invalid event reported: " + event); 646 } 647 logResponse(type, mServicePackageName, mComponentName, mSessionId, duration); 648 } 649 dump(@onNull String prefix, @NonNull PrintWriter pw)650 public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { 651 pw.print(prefix); pw.print("sessionId: "); pw.println(mSessionId); 652 pw.print(prefix); pw.print("taskId: "); pw.println(mTaskId); 653 pw.print(prefix); pw.print("component: "); 654 pw.println(mComponentName.flattenToShortString()); 655 pw.print(prefix); pw.print("focusedId: "); pw.println(mFocusedId); 656 if (mFocusedValue != null) { 657 pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue); 658 } 659 if (mLastShownId != null) { 660 pw.print(prefix); pw.print("lastShownId: "); pw.println(mLastShownId); 661 } 662 pw.print(prefix); pw.print("client: "); pw.println(mClient); 663 final String prefix2 = prefix + " "; 664 if (mFillWindow != null) { 665 pw.print(prefix); pw.println("window:"); 666 mFillWindow.dump(prefix2, pw); 667 } 668 if (mSmartSuggestion != null) { 669 pw.print(prefix); pw.println("smartSuggestion:"); 670 mSmartSuggestion.dump(prefix2, pw); 671 } 672 if (mFirstOnSuccessTime > 0) { 673 final long responseTime = mFirstOnSuccessTime - mFirstRequestTime; 674 pw.print(prefix); pw.print("response time: "); 675 formatDuration(responseTime, pw); pw.println(); 676 } 677 678 if (mUiFirstShownTime > 0) { 679 final long uiRenderingTime = mUiFirstShownTime - mFirstRequestTime; 680 pw.print(prefix); pw.print("UI rendering time: "); 681 formatDuration(uiRenderingTime, pw); pw.println(); 682 } 683 684 if (mUiFirstDestroyedTime > 0) { 685 final long uiTotalTime = mUiFirstDestroyedTime - mFirstRequestTime; 686 pw.print(prefix); pw.print("UI life time: "); 687 formatDuration(uiTotalTime, pw); pw.println(); 688 } 689 } 690 destroy()691 private void destroy() { 692 synchronized (mLock) { 693 if (mFillWindow != null) { 694 if (sDebug) Log.d(TAG, "destroying window"); 695 mFillWindow.destroy(); 696 mFillWindow = null; 697 } 698 } 699 } 700 } 701 } 702