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