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