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