blob: cd549301f5d1a6c1637a4632dd4ae80450e0a22c [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 Leme8e156a62019-03-01 17:16:40 -0800256 /** @hide */
Felipe Leme61a17f32019-01-08 11:35:24 -0800257 protected final void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Felipe Leme749b8892018-12-03 16:30:30 -0800258 if (mAutofillProxies != null) {
259 final int size = mAutofillProxies.size();
260 pw.print("Number proxies: "); pw.println(size);
261 for (int i = 0; i < size; i++) {
262 final int sessionId = mAutofillProxies.keyAt(i);
263 final AutofillProxy proxy = mAutofillProxies.valueAt(i);
264 pw.print(i); pw.print(") SessionId="); pw.print(sessionId); pw.println(":");
265 proxy.dump(" ", pw);
266 }
267 }
Felipe Leme61a17f32019-01-08 11:35:24 -0800268 dump(pw, args);
269 }
270
271 /**
Feng Cao3b24ef02019-02-05 15:03:29 -0800272 * Implementation specific {@code dump}. The child class can override the method to provide
273 * additional information about the Service's state into the dumpsys output.
274 *
275 * @param pw The PrintWriter to which you should dump your state. This will be closed for
276 * you after you return.
277 * @param args additional arguments to the dump request.
Felipe Leme61a17f32019-01-08 11:35:24 -0800278 */
279 protected void dump(@NonNull PrintWriter pw,
280 @SuppressWarnings("unused") @NonNull String[] args) {
281 pw.print(getClass().getName()); pw.println(": nothing to dump");
Felipe Leme749b8892018-12-03 16:30:30 -0800282 }
283
284 /** @hide */
285 static final class AutofillProxy {
286
287 static final int REPORT_EVENT_ON_SUCCESS = 1;
288 static final int REPORT_EVENT_UI_SHOWN = 2;
289 static final int REPORT_EVENT_UI_DESTROYED = 3;
290
291 @IntDef(prefix = { "REPORT_EVENT_" }, value = {
292 REPORT_EVENT_ON_SUCCESS,
293 REPORT_EVENT_UI_SHOWN,
294 REPORT_EVENT_UI_DESTROYED
295 })
296 @Retention(RetentionPolicy.SOURCE)
297 @interface ReportEvent{}
298
299
300 private final Object mLock = new Object();
301 private final IAugmentedAutofillManagerClient mClient;
302 private final int mSessionId;
Felipe Leme749b8892018-12-03 16:30:30 -0800303 public final int taskId;
304 public final ComponentName componentName;
Felipe Lemeb49208d2018-12-11 10:25:57 -0800305 @GuardedBy("mLock")
306 private AutofillId mFocusedId;
307 @GuardedBy("mLock")
308 private AutofillValue mFocusedValue;
Feng Cao70a75c92019-02-24 18:18:47 -0800309 @GuardedBy("mLock")
310 private IFillCallback mCallback;
Felipe Leme749b8892018-12-03 16:30:30 -0800311
Felipe Leme6867e702018-12-12 15:56:06 -0800312 /**
313 * Id of the last field that cause the Autofill UI to be shown.
314 *
315 * <p>Used to make sure the SmartSuggestionsParams is updated when a new fields is focused.
316 */
Felipe Leme6867e702018-12-12 15:56:06 -0800317 @GuardedBy("mLock")
318 private AutofillId mLastShownId;
319
Felipe Leme749b8892018-12-03 16:30:30 -0800320 // Objects used to log metrics
Feng Cao70a75c92019-02-24 18:18:47 -0800321 private final long mFirstRequestTime;
322 private long mFirstOnSuccessTime;
Felipe Leme749b8892018-12-03 16:30:30 -0800323 private long mUiFirstShownTime;
324 private long mUiFirstDestroyedTime;
325
326 @GuardedBy("mLock")
327 private SystemPopupPresentationParams mSmartSuggestion;
328
329 @GuardedBy("mLock")
330 private FillWindow mFillWindow;
331
332 private AutofillProxy(int sessionId, @NonNull IBinder client, int taskId,
333 @NonNull ComponentName componentName, @NonNull AutofillId focusedId,
334 @Nullable AutofillValue focusedValue, long requestTime,
335 @NonNull IFillCallback callback) {
336 mSessionId = sessionId;
337 mClient = IAugmentedAutofillManagerClient.Stub.asInterface(client);
338 mCallback = callback;
339 this.taskId = taskId;
340 this.componentName = componentName;
Felipe Lemeb49208d2018-12-11 10:25:57 -0800341 this.mFocusedId = focusedId;
342 this.mFocusedValue = focusedValue;
Feng Cao70a75c92019-02-24 18:18:47 -0800343 this.mFirstRequestTime = requestTime;
Felipe Leme559e21d2019-01-18 17:57:21 -0800344 // TODO(b/123099468): linkToDeath
Felipe Leme749b8892018-12-03 16:30:30 -0800345 }
346
347 @NonNull
348 public SystemPopupPresentationParams getSmartSuggestionParams() {
349 synchronized (mLock) {
Felipe Leme6867e702018-12-12 15:56:06 -0800350 if (mSmartSuggestion != null && mFocusedId.equals(mLastShownId)) {
Felipe Leme749b8892018-12-03 16:30:30 -0800351 return mSmartSuggestion;
352 }
353 Rect rect;
354 try {
Felipe Lemeb49208d2018-12-11 10:25:57 -0800355 rect = mClient.getViewCoordinates(mFocusedId);
Felipe Leme749b8892018-12-03 16:30:30 -0800356 } catch (RemoteException e) {
Felipe Lemeb49208d2018-12-11 10:25:57 -0800357 Log.w(TAG, "Could not get coordinates for " + mFocusedId);
Felipe Leme749b8892018-12-03 16:30:30 -0800358 return null;
359 }
360 if (rect == null) {
Felipe Lemeb49208d2018-12-11 10:25:57 -0800361 if (DEBUG) Log.d(TAG, "getViewCoordinates(" + mFocusedId + ") returned null");
Felipe Leme749b8892018-12-03 16:30:30 -0800362 return null;
363 }
364 mSmartSuggestion = new SystemPopupPresentationParams(this, rect);
Felipe Leme6867e702018-12-12 15:56:06 -0800365 mLastShownId = mFocusedId;
Felipe Leme749b8892018-12-03 16:30:30 -0800366 return mSmartSuggestion;
367 }
368 }
369
370 public void autofill(@NonNull List<Pair<AutofillId, AutofillValue>> pairs)
371 throws RemoteException {
372 final int size = pairs.size();
373 final List<AutofillId> ids = new ArrayList<>(size);
374 final List<AutofillValue> values = new ArrayList<>(size);
375 for (int i = 0; i < size; i++) {
376 final Pair<AutofillId, AutofillValue> pair = pairs.get(i);
377 ids.add(pair.first);
378 values.add(pair.second);
379 }
380 mClient.autofill(mSessionId, ids, values);
381 }
382
383 public void setFillWindow(@NonNull FillWindow fillWindow) {
384 synchronized (mLock) {
385 mFillWindow = fillWindow;
386 }
387 }
388
389 public FillWindow getFillWindow() {
390 synchronized (mLock) {
391 return mFillWindow;
392 }
393 }
394
Feng Cao158b6562019-01-07 15:42:52 -0800395 public void requestShowFillUi(int width, int height, Rect anchorBounds,
396 IAutofillWindowPresenter presenter) throws RemoteException {
397 mClient.requestShowFillUi(mSessionId, mFocusedId, width, height, anchorBounds,
398 presenter);
399 }
400
401 public void requestHideFillUi() throws RemoteException {
402 mClient.requestHideFillUi(mSessionId, mFocusedId);
403 }
404
Feng Cao70a75c92019-02-24 18:18:47 -0800405 private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue,
406 @NonNull IFillCallback callback) {
Felipe Lemeb49208d2018-12-11 10:25:57 -0800407 synchronized (mLock) {
Felipe Leme559e21d2019-01-18 17:57:21 -0800408 // TODO(b/123099468): should we close the popupwindow if the focused id changed?
Felipe Lemeb49208d2018-12-11 10:25:57 -0800409 mFocusedId = focusedId;
410 mFocusedValue = focusedValue;
Feng Cao70a75c92019-02-24 18:18:47 -0800411 if (mCallback != null) {
412 // TODO(b/123101711): we need to check whether the previous request was
413 // completed or not, and if not, cancel it first.
414 Slog.d(TAG, "mCallback is updated.");
415 }
416 mCallback = callback;
Felipe Lemeb49208d2018-12-11 10:25:57 -0800417 }
418 }
419
420 @NonNull
421 public AutofillId getFocusedId() {
422 synchronized (mLock) {
423 return mFocusedId;
424 }
425 }
426
427 @NonNull
428 public AutofillValue getFocusedValue() {
429 synchronized (mLock) {
430 return mFocusedValue;
431 }
432 }
433
Felipe Leme749b8892018-12-03 16:30:30 -0800434 // Used (mostly) for metrics.
435 public void report(@ReportEvent int event) {
436 switch (event) {
437 case REPORT_EVENT_ON_SUCCESS:
Feng Cao70a75c92019-02-24 18:18:47 -0800438 if (mFirstOnSuccessTime == 0) {
439 mFirstOnSuccessTime = SystemClock.elapsedRealtime();
Felipe Leme749b8892018-12-03 16:30:30 -0800440 if (DEBUG) {
Feng Cao70a75c92019-02-24 18:18:47 -0800441 Slog.d(TAG, "Service responded in " + TimeUtils.formatDuration(
442 mFirstOnSuccessTime - mFirstRequestTime));
Felipe Leme749b8892018-12-03 16:30:30 -0800443 }
444 }
445 try {
446 mCallback.onSuccess();
447 } catch (RemoteException e) {
448 Log.e(TAG, "Error reporting success: " + e);
449 }
450 break;
451 case REPORT_EVENT_UI_SHOWN:
452 if (mUiFirstShownTime == 0) {
453 mUiFirstShownTime = SystemClock.elapsedRealtime();
454 if (DEBUG) {
Feng Cao70a75c92019-02-24 18:18:47 -0800455 Slog.d(TAG, "UI shown in " + TimeUtils.formatDuration(
456 mUiFirstShownTime - mFirstRequestTime));
Felipe Leme749b8892018-12-03 16:30:30 -0800457 }
458 }
459 break;
460 case REPORT_EVENT_UI_DESTROYED:
461 if (mUiFirstDestroyedTime == 0) {
462 mUiFirstDestroyedTime = SystemClock.elapsedRealtime();
463 if (DEBUG) {
Feng Cao70a75c92019-02-24 18:18:47 -0800464 Slog.d(TAG, "UI destroyed in " + TimeUtils.formatDuration(
465 mUiFirstDestroyedTime - mFirstRequestTime));
Felipe Leme749b8892018-12-03 16:30:30 -0800466 }
467 }
468 break;
469 default:
470 Slog.w(TAG, "invalid event reported: " + event);
471 }
Felipe Leme559e21d2019-01-18 17:57:21 -0800472 // TODO(b/122858578): log metrics as well
Felipe Leme749b8892018-12-03 16:30:30 -0800473 }
474
475 public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
476 pw.print(prefix); pw.print("sessionId: "); pw.println(mSessionId);
477 pw.print(prefix); pw.print("taskId: "); pw.println(taskId);
478 pw.print(prefix); pw.print("component: ");
479 pw.println(componentName.flattenToShortString());
Felipe Lemeb49208d2018-12-11 10:25:57 -0800480 pw.print(prefix); pw.print("focusedId: "); pw.println(mFocusedId);
481 if (mFocusedValue != null) {
482 pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue);
Felipe Leme749b8892018-12-03 16:30:30 -0800483 }
Felipe Leme6867e702018-12-12 15:56:06 -0800484 if (mLastShownId != null) {
485 pw.print(prefix); pw.print("lastShownId: "); pw.println(mLastShownId);
486 }
Felipe Leme749b8892018-12-03 16:30:30 -0800487 pw.print(prefix); pw.print("client: "); pw.println(mClient);
488 final String prefix2 = prefix + " ";
489 if (mFillWindow != null) {
490 pw.print(prefix); pw.println("window:");
491 mFillWindow.dump(prefix2, pw);
492 }
493 if (mSmartSuggestion != null) {
494 pw.print(prefix); pw.println("smartSuggestion:");
495 mSmartSuggestion.dump(prefix2, pw);
496 }
Feng Cao70a75c92019-02-24 18:18:47 -0800497 if (mFirstOnSuccessTime > 0) {
498 final long responseTime = mFirstOnSuccessTime - mFirstRequestTime;
Felipe Leme749b8892018-12-03 16:30:30 -0800499 pw.print(prefix); pw.print("response time: ");
500 TimeUtils.formatDuration(responseTime, pw); pw.println();
501 }
502
503 if (mUiFirstShownTime > 0) {
Feng Cao70a75c92019-02-24 18:18:47 -0800504 final long uiRenderingTime = mUiFirstShownTime - mFirstRequestTime;
Felipe Leme749b8892018-12-03 16:30:30 -0800505 pw.print(prefix); pw.print("UI rendering time: ");
506 TimeUtils.formatDuration(uiRenderingTime, pw); pw.println();
507 }
508
509 if (mUiFirstDestroyedTime > 0) {
Feng Cao70a75c92019-02-24 18:18:47 -0800510 final long uiTotalTime = mUiFirstDestroyedTime - mFirstRequestTime;
Felipe Leme749b8892018-12-03 16:30:30 -0800511 pw.print(prefix); pw.print("UI life time: ");
512 TimeUtils.formatDuration(uiTotalTime, pw); pw.println();
513 }
514 }
515
516 private void destroy() {
517 synchronized (mLock) {
518 if (mFillWindow != null) {
519 if (DEBUG) Log.d(TAG, "destroying window");
520 mFillWindow.destroy();
Feng Cao70a75c92019-02-24 18:18:47 -0800521 mFillWindow = null;
Felipe Leme749b8892018-12-03 16:30:30 -0800522 }
523 }
524 }
525 }
526}