blob: 463eae681f05cf504e671d4e0e2b388a80ac4e5a [file] [log] [blame]
Felipe Leme749b8892018-12-03 16:30:30 -08001/*
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 */
16package android.service.autofill.augmented;
17
18import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
19
20import android.annotation.CallSuper;
21import android.annotation.IntDef;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.annotation.SystemApi;
Felipe Leme2cab38c2019-01-11 10:39:14 -080025import android.annotation.TestApi;
Felipe Leme749b8892018-12-03 16:30:30 -080026import android.app.Service;
27import android.content.ComponentName;
28import android.content.Intent;
29import android.graphics.Rect;
30import android.os.CancellationSignal;
31import android.os.Handler;
32import android.os.IBinder;
33import android.os.Looper;
34import android.os.RemoteException;
35import android.os.SystemClock;
Felipe Leme749b8892018-12-03 16:30:30 -080036import android.service.autofill.augmented.PresentationParams.SystemPopupPresentationParams;
37import android.util.Log;
38import android.util.Pair;
39import android.util.Slog;
40import android.util.SparseArray;
41import android.util.TimeUtils;
42import android.view.autofill.AutofillId;
43import android.view.autofill.AutofillValue;
44import android.view.autofill.IAugmentedAutofillManagerClient;
Feng Cao158b6562019-01-07 15:42:52 -080045import android.view.autofill.IAutofillWindowPresenter;
Felipe Leme749b8892018-12-03 16:30:30 -080046
47import com.android.internal.annotations.GuardedBy;
48
49import java.io.FileDescriptor;
50import java.io.PrintWriter;
51import java.lang.annotation.Retention;
52import java.lang.annotation.RetentionPolicy;
53import java.util.ArrayList;
54import java.util.List;
55
56/**
57 * A service used to augment the Autofill subsystem by potentially providing autofill data when the
58 * "standard" workflow failed (for example, because the standard AutofillService didn't have data).
59 *
60 * @hide
61 */
62@SystemApi
Felipe Leme2cab38c2019-01-11 10:39:14 -080063@TestApi
Felipe Leme749b8892018-12-03 16:30:30 -080064public abstract class AugmentedAutofillService extends Service {
65
66 private static final String TAG = AugmentedAutofillService.class.getSimpleName();
67
Felipe Leme559e21d2019-01-18 17:57:21 -080068 // TODO(b/123100811): STOPSHIP use dynamic value, or change to false
Felipe Leme749b8892018-12-03 16:30:30 -080069 static final boolean DEBUG = true;
70 static final boolean VERBOSE = false;
71
72 /**
73 * The {@link Intent} that must be declared as handled by the service.
74 * To be supported, the service must also require the
75 * {@link android.Manifest.permission#BIND_AUGMENTED_AUTOFILL_SERVICE} permission so
76 * that other applications can not abuse it.
77 */
78 public static final String SERVICE_INTERFACE =
79 "android.service.autofill.augmented.AugmentedAutofillService";
80
81 private Handler mHandler;
82
83 private SparseArray<AutofillProxy> mAutofillProxies;
84
85 private final IAugmentedAutofillService mInterface = new IAugmentedAutofillService.Stub() {
86
87 @Override
Adam Heae93a012019-02-20 15:57:05 -080088 public void onConnected() {
89 mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnConnected,
90 AugmentedAutofillService.this));
91 }
92
93 @Override
94 public void onDisconnected() {
95 mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnDisconnected,
96 AugmentedAutofillService.this));
97 }
98
99 @Override
Felipe Leme749b8892018-12-03 16:30:30 -0800100 public void onFillRequest(int sessionId, IBinder client, int taskId,
101 ComponentName componentName, AutofillId focusedId, AutofillValue focusedValue,
102 long requestTime, IFillCallback callback) {
103 mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnFillRequest,
104 AugmentedAutofillService.this, sessionId, client, taskId, componentName,
105 focusedId, focusedValue, requestTime, callback));
106 }
107
108 @Override
Feng Cao158b6562019-01-07 15:42:52 -0800109 public void onDestroyAllFillWindowsRequest() {
Felipe Leme749b8892018-12-03 16:30:30 -0800110 mHandler.sendMessage(
Feng Cao158b6562019-01-07 15:42:52 -0800111 obtainMessage(AugmentedAutofillService::handleOnDestroyAllFillWindowsRequest,
112 AugmentedAutofillService.this));
Felipe Leme749b8892018-12-03 16:30:30 -0800113 }
114 };
115
116 @CallSuper
117 @Override
118 public void onCreate() {
119 super.onCreate();
120 mHandler = new Handler(Looper.getMainLooper(), null, true);
121 }
122
123 /** @hide */
124 @Override
125 public final IBinder onBind(Intent intent) {
126 if (SERVICE_INTERFACE.equals(intent.getAction())) {
127 return mInterface.asBinder();
128 }
129 Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
130 return null;
131 }
132
133 @Override
134 public boolean onUnbind(Intent intent) {
135 mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnUnbind,
136 AugmentedAutofillService.this));
137 return false;
138 }
139
Felipe Leme749b8892018-12-03 16:30:30 -0800140 /**
Felipe Lemef3d317c2019-02-14 14:20:33 -0800141 * Called when the Android system connects to service.
142 *
143 * <p>You should generally do initialization here rather than in {@link #onCreate}.
144 */
145 public void onConnected() {
146 }
147
148 /**
Felipe Leme749b8892018-12-03 16:30:30 -0800149 * Asks the service to handle an "augmented" autofill request.
150 *
151 * <p>This method is called when the "stantard" autofill service cannot handle a request, which
152 * typically occurs when:
153 * <ul>
154 * <li>Service does not recognize what should be autofilled.
155 * <li>Service does not have data to fill the request.
156 * <li>Service blacklisted that app (or activity) for autofill.
157 * <li>App disabled itself for autofill.
158 * </ul>
159 *
160 * <p>Differently from the standard autofill workflow, on augmented autofill the service is
161 * responsible to generate the autofill UI and request the Android system to autofill the
162 * activity when the user taps an action in that UI (through the
163 * {@link FillController#autofill(List)} method).
164 *
165 * <p>The service <b>MUST</b> call {@link
166 * FillCallback#onSuccess(android.service.autofill.augmented.FillResponse)} as soon as possible,
167 * passing {@code null} when it cannot fulfill the request.
168 * @param request the request to handle.
169 * @param cancellationSignal signal for observing cancellation requests. The system will use
170 * this to notify you that the fill result is no longer needed and you should stop
171 * handling this fill request in order to save resources.
172 * @param controller object used to interact with the autofill system.
173 * @param callback object used to notify the result of the request. Service <b>must</b> call
174 * {@link FillCallback#onSuccess(android.service.autofill.augmented.FillResponse)}.
175 */
176 public void onFillRequest(@NonNull FillRequest request,
177 @NonNull CancellationSignal cancellationSignal, @NonNull FillController controller,
178 @NonNull FillCallback callback) {
179 }
180
Felipe Lemef3d317c2019-02-14 14:20:33 -0800181 /**
182 * Called when the Android system disconnects from the service.
183 *
184 * <p> At this point this service may no longer be an active {@link AugmentedAutofillService}.
185 */
186 public void onDisconnected() {
187 }
188
Adam Heae93a012019-02-20 15:57:05 -0800189 private void handleOnConnected() {
190 onConnected();
191 }
192
193 private void handleOnDisconnected() {
194 onDisconnected();
195 }
196
Felipe Leme749b8892018-12-03 16:30:30 -0800197 private void handleOnFillRequest(int sessionId, @NonNull IBinder client, int taskId,
198 @NonNull ComponentName componentName, @NonNull AutofillId focusedId,
199 @Nullable AutofillValue focusedValue, long requestTime,
200 @NonNull IFillCallback callback) {
201 if (mAutofillProxies == null) {
202 mAutofillProxies = new SparseArray<>();
203 }
204 AutofillProxy proxy = mAutofillProxies.get(sessionId);
205 if (proxy == null) {
206 proxy = new AutofillProxy(sessionId, client, taskId, componentName, focusedId,
207 focusedValue, requestTime, callback);
208 mAutofillProxies.put(sessionId, proxy);
209 } else {
Felipe Leme559e21d2019-01-18 17:57:21 -0800210 // TODO(b/123099468): figure out if it's ok to reuse the proxy; add logging
Felipe Leme749b8892018-12-03 16:30:30 -0800211 if (DEBUG) Log.d(TAG, "Reusing proxy for session " + sessionId);
Feng Cao70a75c92019-02-24 18:18:47 -0800212 proxy.update(focusedId, focusedValue, callback);
Felipe Leme749b8892018-12-03 16:30:30 -0800213 }
Felipe Leme559e21d2019-01-18 17:57:21 -0800214 // TODO(b/123101711): set cancellation signal
Felipe Leme749b8892018-12-03 16:30:30 -0800215 final CancellationSignal cancellationSignal = null;
216 onFillRequest(new FillRequest(proxy), cancellationSignal, new FillController(proxy),
217 new FillCallback(proxy));
218 }
219
Feng Cao158b6562019-01-07 15:42:52 -0800220 private void handleOnDestroyAllFillWindowsRequest() {
Felipe Leme749b8892018-12-03 16:30:30 -0800221 if (mAutofillProxies != null) {
Feng Cao158b6562019-01-07 15:42:52 -0800222 final int size = mAutofillProxies.size();
223 for (int i = 0; i < size; i++) {
224 final int sessionId = mAutofillProxies.keyAt(i);
225 final AutofillProxy proxy = mAutofillProxies.valueAt(i);
226 if (proxy == null) {
Felipe Leme559e21d2019-01-18 17:57:21 -0800227 // TODO(b/123100811): this might be fine, in which case we should logv it
Feng Cao158b6562019-01-07 15:42:52 -0800228 Log.w(TAG, "No proxy for session " + sessionId);
229 return;
230 }
231 proxy.destroy();
232 }
233 mAutofillProxies.clear();
Felipe Leme749b8892018-12-03 16:30:30 -0800234 }
Felipe Leme749b8892018-12-03 16:30:30 -0800235 }
236
237 private void handleOnUnbind() {
238 if (mAutofillProxies == null) {
239 if (DEBUG) Log.d(TAG, "onUnbind(): no proxy to destroy");
240 return;
241 }
242 final int size = mAutofillProxies.size();
243 if (DEBUG) Log.d(TAG, "onUnbind(): destroying " + size + " proxies");
244 for (int i = 0; i < size; i++) {
245 final AutofillProxy proxy = mAutofillProxies.valueAt(i);
246 try {
247 proxy.destroy();
248 } catch (Exception e) {
249 Log.w(TAG, "error destroying " + proxy);
250 }
251 }
252 mAutofillProxies = null;
253 }
254
255 @Override
Felipe Leme61a17f32019-01-08 11:35:24 -0800256 protected final void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Felipe Leme749b8892018-12-03 16:30:30 -0800257 if (mAutofillProxies != null) {
258 final int size = mAutofillProxies.size();
259 pw.print("Number proxies: "); pw.println(size);
260 for (int i = 0; i < size; i++) {
261 final int sessionId = mAutofillProxies.keyAt(i);
262 final AutofillProxy proxy = mAutofillProxies.valueAt(i);
263 pw.print(i); pw.print(") SessionId="); pw.print(sessionId); pw.println(":");
264 proxy.dump(" ", pw);
265 }
266 }
Felipe Leme61a17f32019-01-08 11:35:24 -0800267 dump(pw, args);
268 }
269
270 /**
Feng Cao3b24ef02019-02-05 15:03:29 -0800271 * Implementation specific {@code dump}. The child class can override the method to provide
272 * additional information about the Service's state into the dumpsys output.
273 *
274 * @param pw The PrintWriter to which you should dump your state. This will be closed for
275 * you after you return.
276 * @param args additional arguments to the dump request.
Felipe Leme61a17f32019-01-08 11:35:24 -0800277 */
278 protected void dump(@NonNull PrintWriter pw,
279 @SuppressWarnings("unused") @NonNull String[] args) {
280 pw.print(getClass().getName()); pw.println(": nothing to dump");
Felipe Leme749b8892018-12-03 16:30:30 -0800281 }
282
283 /** @hide */
284 static final class AutofillProxy {
285
286 static final int REPORT_EVENT_ON_SUCCESS = 1;
287 static final int REPORT_EVENT_UI_SHOWN = 2;
288 static final int REPORT_EVENT_UI_DESTROYED = 3;
289
290 @IntDef(prefix = { "REPORT_EVENT_" }, value = {
291 REPORT_EVENT_ON_SUCCESS,
292 REPORT_EVENT_UI_SHOWN,
293 REPORT_EVENT_UI_DESTROYED
294 })
295 @Retention(RetentionPolicy.SOURCE)
296 @interface ReportEvent{}
297
298
299 private final Object mLock = new Object();
300 private final IAugmentedAutofillManagerClient mClient;
301 private final int mSessionId;
Felipe Leme749b8892018-12-03 16:30:30 -0800302 public final int taskId;
303 public final ComponentName componentName;
Felipe Lemeb49208d2018-12-11 10:25:57 -0800304 @GuardedBy("mLock")
305 private AutofillId mFocusedId;
306 @GuardedBy("mLock")
307 private AutofillValue mFocusedValue;
Feng Cao70a75c92019-02-24 18:18:47 -0800308 @GuardedBy("mLock")
309 private IFillCallback mCallback;
Felipe Leme749b8892018-12-03 16:30:30 -0800310
Felipe Leme6867e702018-12-12 15:56:06 -0800311 /**
312 * Id of the last field that cause the Autofill UI to be shown.
313 *
314 * <p>Used to make sure the SmartSuggestionsParams is updated when a new fields is focused.
315 */
Felipe Leme6867e702018-12-12 15:56:06 -0800316 @GuardedBy("mLock")
317 private AutofillId mLastShownId;
318
Felipe Leme749b8892018-12-03 16:30:30 -0800319 // Objects used to log metrics
Feng Cao70a75c92019-02-24 18:18:47 -0800320 private final long mFirstRequestTime;
321 private long mFirstOnSuccessTime;
Felipe Leme749b8892018-12-03 16:30:30 -0800322 private long mUiFirstShownTime;
323 private long mUiFirstDestroyedTime;
324
325 @GuardedBy("mLock")
326 private SystemPopupPresentationParams mSmartSuggestion;
327
328 @GuardedBy("mLock")
329 private FillWindow mFillWindow;
330
331 private AutofillProxy(int sessionId, @NonNull IBinder client, int taskId,
332 @NonNull ComponentName componentName, @NonNull AutofillId focusedId,
333 @Nullable AutofillValue focusedValue, long requestTime,
334 @NonNull IFillCallback callback) {
335 mSessionId = sessionId;
336 mClient = IAugmentedAutofillManagerClient.Stub.asInterface(client);
337 mCallback = callback;
338 this.taskId = taskId;
339 this.componentName = componentName;
Felipe Lemeb49208d2018-12-11 10:25:57 -0800340 this.mFocusedId = focusedId;
341 this.mFocusedValue = focusedValue;
Feng Cao70a75c92019-02-24 18:18:47 -0800342 this.mFirstRequestTime = requestTime;
Felipe Leme559e21d2019-01-18 17:57:21 -0800343 // TODO(b/123099468): linkToDeath
Felipe Leme749b8892018-12-03 16:30:30 -0800344 }
345
346 @NonNull
347 public SystemPopupPresentationParams getSmartSuggestionParams() {
348 synchronized (mLock) {
Felipe Leme6867e702018-12-12 15:56:06 -0800349 if (mSmartSuggestion != null && mFocusedId.equals(mLastShownId)) {
Felipe Leme749b8892018-12-03 16:30:30 -0800350 return mSmartSuggestion;
351 }
352 Rect rect;
353 try {
Felipe Lemeb49208d2018-12-11 10:25:57 -0800354 rect = mClient.getViewCoordinates(mFocusedId);
Felipe Leme749b8892018-12-03 16:30:30 -0800355 } catch (RemoteException e) {
Felipe Lemeb49208d2018-12-11 10:25:57 -0800356 Log.w(TAG, "Could not get coordinates for " + mFocusedId);
Felipe Leme749b8892018-12-03 16:30:30 -0800357 return null;
358 }
359 if (rect == null) {
Felipe Lemeb49208d2018-12-11 10:25:57 -0800360 if (DEBUG) Log.d(TAG, "getViewCoordinates(" + mFocusedId + ") returned null");
Felipe Leme749b8892018-12-03 16:30:30 -0800361 return null;
362 }
363 mSmartSuggestion = new SystemPopupPresentationParams(this, rect);
Felipe Leme6867e702018-12-12 15:56:06 -0800364 mLastShownId = mFocusedId;
Felipe Leme749b8892018-12-03 16:30:30 -0800365 return mSmartSuggestion;
366 }
367 }
368
369 public void autofill(@NonNull List<Pair<AutofillId, AutofillValue>> pairs)
370 throws RemoteException {
371 final int size = pairs.size();
372 final List<AutofillId> ids = new ArrayList<>(size);
373 final List<AutofillValue> values = new ArrayList<>(size);
374 for (int i = 0; i < size; i++) {
375 final Pair<AutofillId, AutofillValue> pair = pairs.get(i);
376 ids.add(pair.first);
377 values.add(pair.second);
378 }
379 mClient.autofill(mSessionId, ids, values);
380 }
381
382 public void setFillWindow(@NonNull FillWindow fillWindow) {
383 synchronized (mLock) {
384 mFillWindow = fillWindow;
385 }
386 }
387
388 public FillWindow getFillWindow() {
389 synchronized (mLock) {
390 return mFillWindow;
391 }
392 }
393
Feng Cao158b6562019-01-07 15:42:52 -0800394 public void requestShowFillUi(int width, int height, Rect anchorBounds,
395 IAutofillWindowPresenter presenter) throws RemoteException {
396 mClient.requestShowFillUi(mSessionId, mFocusedId, width, height, anchorBounds,
397 presenter);
398 }
399
400 public void requestHideFillUi() throws RemoteException {
401 mClient.requestHideFillUi(mSessionId, mFocusedId);
402 }
403
Feng Cao70a75c92019-02-24 18:18:47 -0800404 private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue,
405 @NonNull IFillCallback callback) {
Felipe Lemeb49208d2018-12-11 10:25:57 -0800406 synchronized (mLock) {
Felipe Leme559e21d2019-01-18 17:57:21 -0800407 // TODO(b/123099468): should we close the popupwindow if the focused id changed?
Felipe Lemeb49208d2018-12-11 10:25:57 -0800408 mFocusedId = focusedId;
409 mFocusedValue = focusedValue;
Feng Cao70a75c92019-02-24 18:18:47 -0800410 if (mCallback != null) {
411 // TODO(b/123101711): we need to check whether the previous request was
412 // completed or not, and if not, cancel it first.
413 Slog.d(TAG, "mCallback is updated.");
414 }
415 mCallback = callback;
Felipe Lemeb49208d2018-12-11 10:25:57 -0800416 }
417 }
418
419 @NonNull
420 public AutofillId getFocusedId() {
421 synchronized (mLock) {
422 return mFocusedId;
423 }
424 }
425
426 @NonNull
427 public AutofillValue getFocusedValue() {
428 synchronized (mLock) {
429 return mFocusedValue;
430 }
431 }
432
Felipe Leme749b8892018-12-03 16:30:30 -0800433 // Used (mostly) for metrics.
434 public void report(@ReportEvent int event) {
435 switch (event) {
436 case REPORT_EVENT_ON_SUCCESS:
Feng Cao70a75c92019-02-24 18:18:47 -0800437 if (mFirstOnSuccessTime == 0) {
438 mFirstOnSuccessTime = SystemClock.elapsedRealtime();
Felipe Leme749b8892018-12-03 16:30:30 -0800439 if (DEBUG) {
Feng Cao70a75c92019-02-24 18:18:47 -0800440 Slog.d(TAG, "Service responded in " + TimeUtils.formatDuration(
441 mFirstOnSuccessTime - mFirstRequestTime));
Felipe Leme749b8892018-12-03 16:30:30 -0800442 }
443 }
444 try {
445 mCallback.onSuccess();
446 } catch (RemoteException e) {
447 Log.e(TAG, "Error reporting success: " + e);
448 }
449 break;
450 case REPORT_EVENT_UI_SHOWN:
451 if (mUiFirstShownTime == 0) {
452 mUiFirstShownTime = SystemClock.elapsedRealtime();
453 if (DEBUG) {
Feng Cao70a75c92019-02-24 18:18:47 -0800454 Slog.d(TAG, "UI shown in " + TimeUtils.formatDuration(
455 mUiFirstShownTime - mFirstRequestTime));
Felipe Leme749b8892018-12-03 16:30:30 -0800456 }
457 }
458 break;
459 case REPORT_EVENT_UI_DESTROYED:
460 if (mUiFirstDestroyedTime == 0) {
461 mUiFirstDestroyedTime = SystemClock.elapsedRealtime();
462 if (DEBUG) {
Feng Cao70a75c92019-02-24 18:18:47 -0800463 Slog.d(TAG, "UI destroyed in " + TimeUtils.formatDuration(
464 mUiFirstDestroyedTime - mFirstRequestTime));
Felipe Leme749b8892018-12-03 16:30:30 -0800465 }
466 }
467 break;
468 default:
469 Slog.w(TAG, "invalid event reported: " + event);
470 }
Felipe Leme559e21d2019-01-18 17:57:21 -0800471 // TODO(b/122858578): log metrics as well
Felipe Leme749b8892018-12-03 16:30:30 -0800472 }
473
474 public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
475 pw.print(prefix); pw.print("sessionId: "); pw.println(mSessionId);
476 pw.print(prefix); pw.print("taskId: "); pw.println(taskId);
477 pw.print(prefix); pw.print("component: ");
478 pw.println(componentName.flattenToShortString());
Felipe Lemeb49208d2018-12-11 10:25:57 -0800479 pw.print(prefix); pw.print("focusedId: "); pw.println(mFocusedId);
480 if (mFocusedValue != null) {
481 pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue);
Felipe Leme749b8892018-12-03 16:30:30 -0800482 }
Felipe Leme6867e702018-12-12 15:56:06 -0800483 if (mLastShownId != null) {
484 pw.print(prefix); pw.print("lastShownId: "); pw.println(mLastShownId);
485 }
Felipe Leme749b8892018-12-03 16:30:30 -0800486 pw.print(prefix); pw.print("client: "); pw.println(mClient);
487 final String prefix2 = prefix + " ";
488 if (mFillWindow != null) {
489 pw.print(prefix); pw.println("window:");
490 mFillWindow.dump(prefix2, pw);
491 }
492 if (mSmartSuggestion != null) {
493 pw.print(prefix); pw.println("smartSuggestion:");
494 mSmartSuggestion.dump(prefix2, pw);
495 }
Feng Cao70a75c92019-02-24 18:18:47 -0800496 if (mFirstOnSuccessTime > 0) {
497 final long responseTime = mFirstOnSuccessTime - mFirstRequestTime;
Felipe Leme749b8892018-12-03 16:30:30 -0800498 pw.print(prefix); pw.print("response time: ");
499 TimeUtils.formatDuration(responseTime, pw); pw.println();
500 }
501
502 if (mUiFirstShownTime > 0) {
Feng Cao70a75c92019-02-24 18:18:47 -0800503 final long uiRenderingTime = mUiFirstShownTime - mFirstRequestTime;
Felipe Leme749b8892018-12-03 16:30:30 -0800504 pw.print(prefix); pw.print("UI rendering time: ");
505 TimeUtils.formatDuration(uiRenderingTime, pw); pw.println();
506 }
507
508 if (mUiFirstDestroyedTime > 0) {
Feng Cao70a75c92019-02-24 18:18:47 -0800509 final long uiTotalTime = mUiFirstDestroyedTime - mFirstRequestTime;
Felipe Leme749b8892018-12-03 16:30:30 -0800510 pw.print(prefix); pw.print("UI life time: ");
511 TimeUtils.formatDuration(uiTotalTime, pw); pw.println();
512 }
513 }
514
515 private void destroy() {
516 synchronized (mLock) {
517 if (mFillWindow != null) {
518 if (DEBUG) Log.d(TAG, "destroying window");
519 mFillWindow.destroy();
Feng Cao70a75c92019-02-24 18:18:47 -0800520 mFillWindow = null;
Felipe Leme749b8892018-12-03 16:30:30 -0800521 }
522 }
523 }
524 }
525}