Merge "Refactored ViewState." into oc-dev

This commit is contained in:
TreeHugger Robot
2017-03-31 03:29:40 +00:00
committed by Android (Google) Code Review
2 changed files with 174 additions and 79 deletions

View File

@@ -63,6 +63,7 @@ import com.android.internal.os.HandlerCaller;
import com.android.server.autofill.ui.AutoFillUI; import com.android.server.autofill.ui.AutoFillUI;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -101,23 +102,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock") @GuardedBy("mLock")
private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>(); private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>();
/**
* Id of the View currently being displayed.
*/
@GuardedBy("mLock") @GuardedBy("mLock")
@Nullable @Nullable private AutofillId mCurrentViewId;
private ViewState mCurrentViewState;
private final IAutoFillManagerClient mClient; private final IAutoFillManagerClient mClient;
@GuardedBy("mLock") @GuardedBy("mLock")
RemoteFillService mRemoteFillService; RemoteFillService mRemoteFillService;
// TODO(b/33197203): Get a response per view instead of per activity. // TODO(b/33197203 , b/35707731): Use List once it supports partitioning
@GuardedBy("mLock") @GuardedBy("mLock")
private FillResponse mCurrentResponse; private FillResponse mCurrentResponse;
/** /**
* Used to remember which {@link Dataset} filled the session. * Used to remember which {@link Dataset} filled the session.
*/ */
// TODO(b/33197203): might need more than one once we support partitions // TODO(b/33197203 , b/35707731): might need more than one once it supports partitioning
@GuardedBy("mLock") @GuardedBy("mLock")
private Dataset mAutoFilledDataset; private Dataset mAutoFilledDataset;
@@ -189,7 +192,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
processResponseLocked(response); processResponseLocked(response);
} }
LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST)) final LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
.setType(MetricsEvent.TYPE_SUCCESS) .setType(MetricsEvent.TYPE_SUCCESS)
.setPackageName(mPackageName) .setPackageName(mPackageName)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
@@ -292,8 +295,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
public void requestShowFillUi(AutofillId id, int width, int height, public void requestShowFillUi(AutofillId id, int width, int height,
IAutofillWindowPresenter presenter) { IAutofillWindowPresenter presenter) {
try { try {
final ViewState currentView = mViewStates.get(mCurrentViewId);
mClient.requestShowFillUi(mWindowToken, id, width, height, mClient.requestShowFillUi(mWindowToken, id, width, height,
mCurrentViewState.mVirtualBounds, presenter); currentView.getVirtualBounds(), presenter);
} catch (RemoteException e) { } catch (RemoteException e) {
Slog.e(TAG, "Error requesting to show fill UI", e); Slog.e(TAG, "Error requesting to show fill UI", e);
} }
@@ -376,13 +380,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return true; return true;
} }
// TODO(b/33197203 , b/35707731): refactor excessive calls to getCurrentValue()
boolean allRequiredAreNotEmpty = true; boolean allRequiredAreNotEmpty = true;
boolean atLeastOneChanged = false; boolean atLeastOneChanged = false;
for (int i = 0; i < requiredIds.length; i++) { for (int i = 0; i < requiredIds.length; i++) {
final AutofillId id = requiredIds[i]; final AutofillId id = requiredIds[i];
final ViewState state = mViewStates.get(id); final ViewState state = mViewStates.get(id);
if (state == null || state.mAutofillValue == null if (state == null || state.getCurrentValue() == null
|| state.mAutofillValue.isEmpty()) { || state.getCurrentValue().isEmpty()) {
final ViewNode node = findViewNodeByIdLocked(id); final ViewNode node = findViewNodeByIdLocked(id);
if (node == null) { if (node == null) {
Slog.w(TAG, "Service passed invalid id on SavableInfo: " + id); Slog.w(TAG, "Service passed invalid id on SavableInfo: " + id);
@@ -398,20 +403,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
break; break;
} }
} }
if (state.mValueUpdated) { if (state.isChanged()) {
final AutofillValue filledValue = findValue(mAutoFilledDataset, id); final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
if (!state.mAutofillValue.equals(filledValue)) { if (!state.getCurrentValue().equals(filledValue)) {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": " Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": "
+ filledValue + " => " + state.mAutofillValue); + filledValue + " => " + state.getCurrentValue());
} }
atLeastOneChanged = true; atLeastOneChanged = true;
} }
} else { } else {
if (state.mAutofillValue == null || state.mAutofillValue.isEmpty()) { if (state.getCurrentValue() == null || state.getCurrentValue().isEmpty()) {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "finishSessionLocked(): empty value for " + id + ": " Slog.d(TAG, "finishSessionLocked(): empty value for " + id + ": "
+ state.mAutofillValue); + state.getCurrentValue());
} }
allRequiredAreNotEmpty = false; allRequiredAreNotEmpty = false;
break; break;
@@ -425,13 +430,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
for (int i = 0; i < saveInfo.getOptionalIds().length; i++) { for (int i = 0; i < saveInfo.getOptionalIds().length; i++) {
final AutofillId id = saveInfo.getOptionalIds()[i]; final AutofillId id = saveInfo.getOptionalIds()[i];
final ViewState state = mViewStates.get(id); final ViewState state = mViewStates.get(id);
if (state != null && state.mAutofillValue != null && state.mValueUpdated) { if (state != null && state.getCurrentValue() != null && state.isChanged()) {
final AutofillValue filledValue = findValue(mAutoFilledDataset, id); final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
if (!state.mAutofillValue.equals(filledValue)) { if (!state.getCurrentValue().equals(filledValue)) {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "finishSessionLocked(): found a change on optional " Slog.d(TAG, "finishSessionLocked(): found a change on optional "
+ id + ": " + filledValue + " => " + id + ": " + filledValue + " => "
+ state.mAutofillValue); + state.getCurrentValue());
} }
atLeastOneChanged = true; atLeastOneChanged = true;
break; break;
@@ -464,7 +469,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final Bundle extras = this.mCurrentResponse.getExtras(); final Bundle extras = this.mCurrentResponse.getExtras();
for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
final AutofillValue value = entry.getValue().mAutofillValue; final AutofillValue value = entry.getValue().getCurrentValue();
if (value == null) { if (value == null) {
if (VERBOSE) { if (VERBOSE) {
Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey()); Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey());
@@ -498,39 +503,43 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) { void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) {
if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) { if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) {
// TODO(b/33197203): ignoring because we don't support partitions yet // TODO(b/33197203): ignoring because we don't support partitions yet
Slog.d(TAG, "updateLocked(): ignoring " + flags + " after app was autofilled"); Slog.d(TAG, "updateLocked(): ignoring " + id + " after app was autofilled");
return; return;
} }
ViewState viewState = mViewStates.get(id); ViewState viewState = mViewStates.get(id);
if (viewState == null) { if (viewState == null) {
viewState = new ViewState(this, id, this); viewState = new ViewState(this, id, this, ViewState.STATE_INITIAL);
mViewStates.put(id, viewState); mViewStates.put(id, viewState);
} }
if ((flags & FLAG_START_SESSION) != 0) { if ((flags & FLAG_START_SESSION) != 0) {
// View is triggering autofill. // View is triggering autofill.
mCurrentViewState = viewState; mCurrentViewId = viewState.id;
viewState.update(value, virtualBounds); viewState.update(value, virtualBounds);
viewState.setState(ViewState.STATE_STARTED_SESSION);
return; return;
} }
if ((flags & FLAG_VALUE_CHANGED) != 0) { if ((flags & FLAG_VALUE_CHANGED) != 0) {
if (value != null && !value.equals(viewState.mAutofillValue)) { if (value != null && !value.equals(viewState.getCurrentValue())) {
viewState.mValueUpdated = true;
// TODO(b/33197203 , b/35707731): currently resets STATE_AUTOFILLED; should check
// first (doesn't make a difference now, but it will when it supports partitions)
viewState.setState(ViewState.STATE_CHANGED);
// Must check if this update was caused by autofilling the view, in which // Must check if this update was caused by autofilling the view, in which
// case we just update the value, but not the UI. // case we just update the value, but not the UI.
if (mAutoFilledDataset != null) { if (mAutoFilledDataset != null) {
final AutofillValue filledValue = findValue(mAutoFilledDataset, id); final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
if (value.equals(filledValue)) { if (value.equals(filledValue)) {
viewState.mAutofillValue = value; viewState.setCurrentValue(value);
return; return;
} }
} }
// Change value // Change value
viewState.mAutofillValue = value; viewState.setCurrentValue(value);
// Update the chooser UI // Update the chooser UI
if (value.isText()) { if (value.isText()) {
@@ -545,15 +554,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if ((flags & FLAG_VIEW_ENTERED) != 0) { if ((flags & FLAG_VIEW_ENTERED) != 0) {
// Remove the UI if the ViewState has changed. // Remove the UI if the ViewState has changed.
if (mCurrentViewState != viewState) { if (mCurrentViewId != viewState.id) {
mUi.hideFillUi(mCurrentViewState != null ? mCurrentViewState.mId : null); mUi.hideFillUi(mCurrentViewId != null ? mCurrentViewId : null);
mCurrentViewState = viewState; mCurrentViewId = viewState.id;
} }
// If the ViewState is ready to be displayed, onReady() will be called. // If the ViewState is ready to be displayed, onReady() will be called.
viewState.update(value, virtualBounds); viewState.update(value, virtualBounds);
// TODO(b/33197203): Remove when there is a response per activity.
if (mCurrentResponse != null) { if (mCurrentResponse != null) {
viewState.setResponse(mCurrentResponse); viewState.setResponse(mCurrentResponse);
} }
@@ -562,9 +570,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
} }
if ((flags & FLAG_VIEW_EXITED) != 0) { if ((flags & FLAG_VIEW_EXITED) != 0) {
if (mCurrentViewState == viewState) { if (mCurrentViewId == viewState.id) {
mUi.hideFillUi(viewState.mId); mUi.hideFillUi(viewState.id);
mCurrentViewState = null; mCurrentViewId = null;
} }
return; return;
} }
@@ -584,17 +592,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
} }
private void notifyUnavailableToClient() { private void notifyUnavailableToClient() {
if (mCurrentViewState == null) { if (mCurrentViewId == null) {
// TODO(b/33197203): temporary sanity check; should never happen // TODO(b/33197203): temporary sanity check; should never happen
Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null"); Slog.w(TAG, "notifyUnavailable(): mCurrentViewId is null");
return; return;
} }
if (!mHasCallback) return; if (!mHasCallback) return;
try { try {
mClient.notifyNoFillUi(mWindowToken, mCurrentViewState.mId); mClient.notifyNoFillUi(mWindowToken, mCurrentViewId);
} catch (RemoteException e) { } catch (RemoteException e) {
Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken
+ " id=" + mCurrentViewState.mId, e); + " id=" + mCurrentViewId, e);
} }
} }
@@ -604,21 +612,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
+ "):" + response); + "):" + response);
} }
if (mCurrentViewState == null) { if (mCurrentViewId == null) {
// TODO(b/33197203): temporary sanity check; should never happen // TODO(b/33197203): temporary sanity check; should never happen
Slog.w(TAG, "processResponseLocked(): mCurrentViewState is null"); Slog.w(TAG, "processResponseLocked(): mCurrentViewId is null");
return; return;
} }
mCurrentResponse = response; mCurrentResponse = response;
if (mCurrentResponse.getAuthentication() != null) { setViewStatesLocked(response, ViewState.STATE_FILLABLE);
// Handle authentication.
final Intent fillInIntent = createAuthFillInIntent(mStructure,
mCurrentResponse.getExtras());
mCurrentViewState.setResponse(mCurrentResponse, fillInIntent);
return;
}
if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null
&& response.getDatasets().size() == 1) { && response.getDatasets().size() == 1) {
@@ -627,7 +629,50 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return; return;
} }
mCurrentViewState.setResponse(mCurrentResponse); // Updates the UI, if necessary.
final ViewState currentView = mViewStates.get(mCurrentViewId);
currentView.maybeCallOnFillReady();
}
/**
* Sets the state of all views in the given response.
*/
private void setViewStatesLocked(FillResponse response, int state) {
final ArrayList<Dataset> datasets = response.getDatasets();
if (datasets != null) {
for (int i = 0; i < datasets.size(); i++) {
final Dataset dataset = datasets.get(i);
if (dataset == null) {
Slog.w(TAG, "Ignoring null dataset on " + datasets);
continue;
}
setViewStatesLocked(response, dataset, state);
}
}
}
/**
* Sets the state of all views in the given dataset and response.
*/
private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset,
int state) {
final ArrayList<AutofillId> ids = dataset.getFieldIds();
for (int j = 0; j < ids.size(); j++) {
final AutofillId id = ids.get(j);
ViewState viewState = mViewStates.get(id);
if (viewState != null) {
viewState.setState(state);
} else {
viewState = new ViewState(this, id, this, state);
if (DEBUG) { // TODO(b/33197203): change to VERBOSE once stable
Slog.d(TAG, "Adding autofillable view with id " + id + " and state " + state);
}
mViewStates.put(id, viewState);
}
if (response != null) {
viewState.setResponse(response);
}
}
} }
void autoFill(Dataset dataset) { void autoFill(Dataset dataset) {
@@ -650,6 +695,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return mService.getServiceName(); return mService.getServiceName();
} }
FillResponse getCurrentResponse() {
return mCurrentResponse;
}
private Intent createAuthFillInIntent(AssistStructure structure, Bundle extras) { private Intent createAuthFillInIntent(AssistStructure structure, Bundle extras) {
final Intent fillInIntent = new Intent(); final Intent fillInIntent = new Intent();
fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure); fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure);
@@ -672,8 +721,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags); pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags);
pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse); pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset); pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset);
pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState); pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId);
pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size()); pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
final String prefix2 = prefix + " "; final String prefix2 = prefix + " ";
for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey()); pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
@@ -700,6 +749,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
} }
mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues()); mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues());
setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED);
} catch (RemoteException e) { } catch (RemoteException e) {
Slog.w(TAG, "Error autofilling activity: " + e); Slog.w(TAG, "Error autofilling activity: " + e);
} }

View File

@@ -17,9 +17,9 @@
package com.android.server.autofill; package com.android.server.autofill;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Intent;
import android.graphics.Rect; import android.graphics.Rect;
import android.service.autofill.FillResponse; import android.service.autofill.FillResponse;
import android.util.DebugUtils;
import android.view.autofill.AutofillId; import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue; import android.view.autofill.AutofillValue;
@@ -40,55 +40,92 @@ final class ViewState {
@Nullable AutofillValue value); @Nullable AutofillValue value);
} }
final AutofillId mId; // NOTE: state constants must be public because of flagstoString().
public static final int STATE_UNKNOWN = 0x00;
/** Initial state. */
public static final int STATE_INITIAL = 0x01;
/** View id is present in a dataset returned by the service. */
public static final int STATE_FILLABLE = 0x02;
/** View was autofilled after user selected a dataset. */
public static final int STATE_AUTOFILLED = 0x04;
/** View value was changed, but not by the service. */
public static final int STATE_CHANGED = 0x08;
/** Set only in the View that started a session . */
public static final int STATE_STARTED_SESSION = 0x10;
public final AutofillId id;
private final Listener mListener; private final Listener mListener;
// TODO(b/33197203): would not need a reference to response and session if it was an inner
// class of Session...
private final Session mSession; private final Session mSession;
private FillResponse mResponse; private FillResponse mResponse;
private Intent mAuthIntent;
// TODO(b/33197203): encapsulate access so it's not called by UI private AutofillValue mCurrentValue;
AutofillValue mAutofillValue; private Rect mVirtualBounds;
// TODO(b/33197203): encapsulate access so it's not called by UI private int mState;
// Bounds if a virtual view, null otherwise
Rect mVirtualBounds;
boolean mValueUpdated; ViewState(Session session, AutofillId id, Listener listener, int state) {
ViewState(Session session, AutofillId id, Listener listener) {
mSession = session; mSession = session;
mId = id; this.id = id;
mListener = listener; mListener = listener;
mState = state;
} }
/** /**
* Response should only be set once. * Gets the boundaries of the virtual view, or {@code null} if the the view is not virtual.
*/ */
@Nullable
Rect getVirtualBounds() {
return mVirtualBounds;
}
/**
* Gets the current value of the view.
*/
@Nullable
AutofillValue getCurrentValue() {
return mCurrentValue;
}
void setResponse(FillResponse response) { void setResponse(FillResponse response) {
mResponse = response; mResponse = response;
maybeCallOnFillReady();
} }
/** FillResponse getResponse() {
* Used when a {@link FillResponse} requires authentication to be unlocked. return mResponse;
*/
void setResponse(FillResponse response, Intent authIntent) {
mAuthIntent = authIntent;
setResponse(response);
} }
CharSequence getServiceName() { CharSequence getServiceName() {
return mSession.getServiceName(); return mSession.getServiceName();
} }
boolean isChanged() {
return (mState & STATE_CHANGED) != 0;
}
int getState() {
return mState;
}
String getStateAsString() {
return DebugUtils.flagsToString(ViewState.class, "STATE_", mState);
}
void setCurrentValue(AutofillValue value) {
mCurrentValue = value;
}
void setState(int state) {
// TODO(b/33197203 , b/35707731): currently it's always setting one state, but once it
// supports partitioning it will need to 'or' some of them..
mState = state;
}
// TODO(b/33197203): need to refactor / rename / document this method to make it clear that // TODO(b/33197203): need to refactor / rename / document this method to make it clear that
// it can change the value and update the UI; similarly, should replace code that // it can change the value and update the UI; similarly, should replace code that
// directly sets mAutoFilLValue to use encapsulation. // directly sets mAutoFilLValue to use encapsulation.
void update(@Nullable AutofillValue autofillValue, @Nullable Rect virtualBounds) { void update(@Nullable AutofillValue autofillValue, @Nullable Rect virtualBounds) {
if (autofillValue != null) { if (autofillValue != null) {
mAutofillValue = autofillValue; mCurrentValue = autofillValue;
} }
if (virtualBounds != null) { if (virtualBounds != null) {
mVirtualBounds = virtualBounds; mVirtualBounds = virtualBounds;
@@ -103,23 +140,31 @@ final class ViewState {
* fill UI is ready to be displayed (i.e. when response and bounds are set). * fill UI is ready to be displayed (i.e. when response and bounds are set).
*/ */
void maybeCallOnFillReady() { void maybeCallOnFillReady() {
if (mResponse != null && (mResponse.getAuthentication() != null // First try the current response associated with this View.
|| mResponse.getDatasets() != null)) { if (mResponse != null) {
mListener.onFillReady(mResponse, mId, mAutofillValue); if (mResponse.getDatasets() != null) {
mListener.onFillReady(mResponse, this.id, mCurrentValue);
}
return;
}
// Then checks if the session has a response waiting authentication; if so, uses it instead.
final FillResponse currentResponse = mSession.getCurrentResponse();
if (currentResponse.getAuthentication() != null) {
mListener.onFillReady(currentResponse, this.id, mCurrentValue);
} }
} }
@Override @Override
public String toString() { public String toString() {
return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mVirtualBounds return "ViewState: [id=" + id + ", currentValue=" + mCurrentValue
+ ", updated = " + mValueUpdated + "]"; + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() +"]";
} }
void dump(String prefix, PrintWriter pw) { void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("id:" ); pw.println(mId); pw.print(prefix); pw.print("id:" ); pw.println(this.id);
pw.print(prefix); pw.print("value:" ); pw.println(mAutofillValue); pw.print(prefix); pw.print("state:" ); pw.println(getStateAsString());
pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated); pw.print(prefix); pw.print("has response:" ); pw.println(mResponse != null);
pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue);
pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds); pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent);
} }
} }