Loading services/autofill/java/com/android/server/autofill/Session.java +97 −47 Original line number Diff line number Diff line Loading @@ -63,6 +63,7 @@ import com.android.internal.os.HandlerCaller; import com.android.server.autofill.ui.AutoFillUI; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Map; import java.util.Map.Entry; Loading Loading @@ -101,23 +102,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>(); /** * Id of the View currently being displayed. */ @GuardedBy("mLock") @Nullable private ViewState mCurrentViewState; @Nullable private AutofillId mCurrentViewId; private final IAutoFillManagerClient mClient; @GuardedBy("mLock") 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") private FillResponse mCurrentResponse; /** * 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") private Dataset mAutoFilledDataset; Loading Loading @@ -189,7 +192,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState processResponseLocked(response); } LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST)) final LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST)) .setType(MetricsEvent.TYPE_SUCCESS) .setPackageName(mPackageName) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, Loading Loading @@ -292,8 +295,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void requestShowFillUi(AutofillId id, int width, int height, IAutofillWindowPresenter presenter) { try { final ViewState currentView = mViewStates.get(mCurrentViewId); mClient.requestShowFillUi(mWindowToken, id, width, height, mCurrentViewState.mVirtualBounds, presenter); currentView.getVirtualBounds(), presenter); } catch (RemoteException e) { Slog.e(TAG, "Error requesting to show fill UI", e); } Loading Loading @@ -376,13 +380,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return true; } // TODO(b/33197203 , b/35707731): refactor excessive calls to getCurrentValue() boolean allRequiredAreNotEmpty = true; boolean atLeastOneChanged = false; for (int i = 0; i < requiredIds.length; i++) { final AutofillId id = requiredIds[i]; final ViewState state = mViewStates.get(id); if (state == null || state.mAutofillValue == null || state.mAutofillValue.isEmpty()) { if (state == null || state.getCurrentValue() == null || state.getCurrentValue().isEmpty()) { final ViewNode node = findViewNodeByIdLocked(id); if (node == null) { Slog.w(TAG, "Service passed invalid id on SavableInfo: " + id); Loading @@ -398,20 +403,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState break; } } if (state.mValueUpdated) { if (state.isChanged()) { final AutofillValue filledValue = findValue(mAutoFilledDataset, id); if (!state.mAutofillValue.equals(filledValue)) { if (!state.getCurrentValue().equals(filledValue)) { if (DEBUG) { Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": " + filledValue + " => " + state.mAutofillValue); + filledValue + " => " + state.getCurrentValue()); } atLeastOneChanged = true; } } else { if (state.mAutofillValue == null || state.mAutofillValue.isEmpty()) { if (state.getCurrentValue() == null || state.getCurrentValue().isEmpty()) { if (DEBUG) { Slog.d(TAG, "finishSessionLocked(): empty value for " + id + ": " + state.mAutofillValue); + state.getCurrentValue()); } allRequiredAreNotEmpty = false; break; Loading @@ -425,13 +430,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState for (int i = 0; i < saveInfo.getOptionalIds().length; i++) { final AutofillId id = saveInfo.getOptionalIds()[i]; 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); if (!state.mAutofillValue.equals(filledValue)) { if (!state.getCurrentValue().equals(filledValue)) { if (DEBUG) { Slog.d(TAG, "finishSessionLocked(): found a change on optional " + id + ": " + filledValue + " => " + state.mAutofillValue); + state.getCurrentValue()); } atLeastOneChanged = true; break; Loading Loading @@ -464,7 +469,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final Bundle extras = this.mCurrentResponse.getExtras(); for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { final AutofillValue value = entry.getValue().mAutofillValue; final AutofillValue value = entry.getValue().getCurrentValue(); if (value == null) { if (VERBOSE) { Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey()); Loading Loading @@ -498,39 +503,43 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) { if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) { // 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; } ViewState viewState = mViewStates.get(id); if (viewState == null) { viewState = new ViewState(this, id, this); viewState = new ViewState(this, id, this, ViewState.STATE_INITIAL); mViewStates.put(id, viewState); } if ((flags & FLAG_START_SESSION) != 0) { // View is triggering autofill. mCurrentViewState = viewState; mCurrentViewId = viewState.id; viewState.update(value, virtualBounds); viewState.setState(ViewState.STATE_STARTED_SESSION); return; } if ((flags & FLAG_VALUE_CHANGED) != 0) { if (value != null && !value.equals(viewState.mAutofillValue)) { viewState.mValueUpdated = true; if (value != null && !value.equals(viewState.getCurrentValue())) { // 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 // case we just update the value, but not the UI. if (mAutoFilledDataset != null) { final AutofillValue filledValue = findValue(mAutoFilledDataset, id); if (value.equals(filledValue)) { viewState.mAutofillValue = value; viewState.setCurrentValue(value); return; } } // Change value viewState.mAutofillValue = value; viewState.setCurrentValue(value); // Update the chooser UI if (value.isText()) { Loading @@ -545,15 +554,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if ((flags & FLAG_VIEW_ENTERED) != 0) { // Remove the UI if the ViewState has changed. if (mCurrentViewState != viewState) { mUi.hideFillUi(mCurrentViewState != null ? mCurrentViewState.mId : null); mCurrentViewState = viewState; if (mCurrentViewId != viewState.id) { mUi.hideFillUi(mCurrentViewId != null ? mCurrentViewId : null); mCurrentViewId = viewState.id; } // If the ViewState is ready to be displayed, onReady() will be called. viewState.update(value, virtualBounds); // TODO(b/33197203): Remove when there is a response per activity. if (mCurrentResponse != null) { viewState.setResponse(mCurrentResponse); } Loading @@ -562,9 +570,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if ((flags & FLAG_VIEW_EXITED) != 0) { if (mCurrentViewState == viewState) { mUi.hideFillUi(viewState.mId); mCurrentViewState = null; if (mCurrentViewId == viewState.id) { mUi.hideFillUi(viewState.id); mCurrentViewId = null; } return; } Loading @@ -584,17 +592,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } private void notifyUnavailableToClient() { if (mCurrentViewState == null) { if (mCurrentViewId == null) { // TODO(b/33197203): temporary sanity check; should never happen Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null"); Slog.w(TAG, "notifyUnavailable(): mCurrentViewId is null"); return; } if (!mHasCallback) return; try { mClient.notifyNoFillUi(mWindowToken, mCurrentViewState.mId); mClient.notifyNoFillUi(mWindowToken, mCurrentViewId); } catch (RemoteException e) { Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken + " id=" + mCurrentViewState.mId, e); + " id=" + mCurrentViewId, e); } } Loading @@ -604,21 +612,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + "):" + response); } if (mCurrentViewState == null) { if (mCurrentViewId == null) { // TODO(b/33197203): temporary sanity check; should never happen Slog.w(TAG, "processResponseLocked(): mCurrentViewState is null"); Slog.w(TAG, "processResponseLocked(): mCurrentViewId is null"); return; } mCurrentResponse = response; if (mCurrentResponse.getAuthentication() != null) { // Handle authentication. final Intent fillInIntent = createAuthFillInIntent(mStructure, mCurrentResponse.getExtras()); mCurrentViewState.setResponse(mCurrentResponse, fillInIntent); return; } setViewStatesLocked(response, ViewState.STATE_FILLABLE); if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null && response.getDatasets().size() == 1) { Loading @@ -627,7 +629,50 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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) { Loading @@ -650,6 +695,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return mService.getServiceName(); } FillResponse getCurrentResponse() { return mCurrentResponse; } private Intent createAuthFillInIntent(AssistStructure structure, Bundle extras) { final Intent fillInIntent = new Intent(); fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure); Loading @@ -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("mCurrentResponse: "); pw.println(mCurrentResponse); pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset); pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState); pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size()); pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId); pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size()); final String prefix2 = prefix + " "; for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey()); Loading @@ -700,6 +749,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); } mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues()); setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED); } catch (RemoteException e) { Slog.w(TAG, "Error autofilling activity: " + e); } Loading services/autofill/java/com/android/server/autofill/ViewState.java +77 −32 Original line number Diff line number Diff line Loading @@ -17,9 +17,9 @@ package com.android.server.autofill; import android.annotation.Nullable; import android.content.Intent; import android.graphics.Rect; import android.service.autofill.FillResponse; import android.util.DebugUtils; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; Loading @@ -40,55 +40,92 @@ final class ViewState { @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; // 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 FillResponse mResponse; private Intent mAuthIntent; // TODO(b/33197203): encapsulate access so it's not called by UI AutofillValue mAutofillValue; private AutofillValue mCurrentValue; private Rect mVirtualBounds; // TODO(b/33197203): encapsulate access so it's not called by UI // Bounds if a virtual view, null otherwise Rect mVirtualBounds; private int mState; boolean mValueUpdated; ViewState(Session session, AutofillId id, Listener listener) { ViewState(Session session, AutofillId id, Listener listener, int state) { mSession = session; mId = id; this.id = id; 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. */ void setResponse(FillResponse response) { mResponse = response; maybeCallOnFillReady(); @Nullable Rect getVirtualBounds() { return mVirtualBounds; } /** * Used when a {@link FillResponse} requires authentication to be unlocked. * Gets the current value of the view. */ void setResponse(FillResponse response, Intent authIntent) { mAuthIntent = authIntent; setResponse(response); @Nullable AutofillValue getCurrentValue() { return mCurrentValue; } void setResponse(FillResponse response) { mResponse = response; } FillResponse getResponse() { return mResponse; } CharSequence 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 // it can change the value and update the UI; similarly, should replace code that // directly sets mAutoFilLValue to use encapsulation. void update(@Nullable AutofillValue autofillValue, @Nullable Rect virtualBounds) { if (autofillValue != null) { mAutofillValue = autofillValue; mCurrentValue = autofillValue; } if (virtualBounds != null) { mVirtualBounds = virtualBounds; Loading @@ -103,23 +140,31 @@ final class ViewState { * fill UI is ready to be displayed (i.e. when response and bounds are set). */ void maybeCallOnFillReady() { if (mResponse != null && (mResponse.getAuthentication() != null || mResponse.getDatasets() != null)) { mListener.onFillReady(mResponse, mId, mAutofillValue); // First try the current response associated with this View. if (mResponse != null) { 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 public String toString() { return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mVirtualBounds + ", updated = " + mValueUpdated + "]"; return "ViewState: [id=" + id + ", currentValue=" + mCurrentValue + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() +"]"; } void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("id:" ); pw.println(mId); pw.print(prefix); pw.print("value:" ); pw.println(mAutofillValue); pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated); pw.print(prefix); pw.print("id:" ); pw.println(this.id); pw.print(prefix); pw.print("state:" ); pw.println(getStateAsString()); 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("authIntent:" ); pw.println(mAuthIntent); } } No newline at end of file Loading
services/autofill/java/com/android/server/autofill/Session.java +97 −47 Original line number Diff line number Diff line Loading @@ -63,6 +63,7 @@ import com.android.internal.os.HandlerCaller; import com.android.server.autofill.ui.AutoFillUI; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Map; import java.util.Map.Entry; Loading Loading @@ -101,23 +102,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>(); /** * Id of the View currently being displayed. */ @GuardedBy("mLock") @Nullable private ViewState mCurrentViewState; @Nullable private AutofillId mCurrentViewId; private final IAutoFillManagerClient mClient; @GuardedBy("mLock") 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") private FillResponse mCurrentResponse; /** * 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") private Dataset mAutoFilledDataset; Loading Loading @@ -189,7 +192,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState processResponseLocked(response); } LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST)) final LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST)) .setType(MetricsEvent.TYPE_SUCCESS) .setPackageName(mPackageName) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, Loading Loading @@ -292,8 +295,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void requestShowFillUi(AutofillId id, int width, int height, IAutofillWindowPresenter presenter) { try { final ViewState currentView = mViewStates.get(mCurrentViewId); mClient.requestShowFillUi(mWindowToken, id, width, height, mCurrentViewState.mVirtualBounds, presenter); currentView.getVirtualBounds(), presenter); } catch (RemoteException e) { Slog.e(TAG, "Error requesting to show fill UI", e); } Loading Loading @@ -376,13 +380,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return true; } // TODO(b/33197203 , b/35707731): refactor excessive calls to getCurrentValue() boolean allRequiredAreNotEmpty = true; boolean atLeastOneChanged = false; for (int i = 0; i < requiredIds.length; i++) { final AutofillId id = requiredIds[i]; final ViewState state = mViewStates.get(id); if (state == null || state.mAutofillValue == null || state.mAutofillValue.isEmpty()) { if (state == null || state.getCurrentValue() == null || state.getCurrentValue().isEmpty()) { final ViewNode node = findViewNodeByIdLocked(id); if (node == null) { Slog.w(TAG, "Service passed invalid id on SavableInfo: " + id); Loading @@ -398,20 +403,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState break; } } if (state.mValueUpdated) { if (state.isChanged()) { final AutofillValue filledValue = findValue(mAutoFilledDataset, id); if (!state.mAutofillValue.equals(filledValue)) { if (!state.getCurrentValue().equals(filledValue)) { if (DEBUG) { Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": " + filledValue + " => " + state.mAutofillValue); + filledValue + " => " + state.getCurrentValue()); } atLeastOneChanged = true; } } else { if (state.mAutofillValue == null || state.mAutofillValue.isEmpty()) { if (state.getCurrentValue() == null || state.getCurrentValue().isEmpty()) { if (DEBUG) { Slog.d(TAG, "finishSessionLocked(): empty value for " + id + ": " + state.mAutofillValue); + state.getCurrentValue()); } allRequiredAreNotEmpty = false; break; Loading @@ -425,13 +430,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState for (int i = 0; i < saveInfo.getOptionalIds().length; i++) { final AutofillId id = saveInfo.getOptionalIds()[i]; 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); if (!state.mAutofillValue.equals(filledValue)) { if (!state.getCurrentValue().equals(filledValue)) { if (DEBUG) { Slog.d(TAG, "finishSessionLocked(): found a change on optional " + id + ": " + filledValue + " => " + state.mAutofillValue); + state.getCurrentValue()); } atLeastOneChanged = true; break; Loading Loading @@ -464,7 +469,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final Bundle extras = this.mCurrentResponse.getExtras(); for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { final AutofillValue value = entry.getValue().mAutofillValue; final AutofillValue value = entry.getValue().getCurrentValue(); if (value == null) { if (VERBOSE) { Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey()); Loading Loading @@ -498,39 +503,43 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) { if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) { // 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; } ViewState viewState = mViewStates.get(id); if (viewState == null) { viewState = new ViewState(this, id, this); viewState = new ViewState(this, id, this, ViewState.STATE_INITIAL); mViewStates.put(id, viewState); } if ((flags & FLAG_START_SESSION) != 0) { // View is triggering autofill. mCurrentViewState = viewState; mCurrentViewId = viewState.id; viewState.update(value, virtualBounds); viewState.setState(ViewState.STATE_STARTED_SESSION); return; } if ((flags & FLAG_VALUE_CHANGED) != 0) { if (value != null && !value.equals(viewState.mAutofillValue)) { viewState.mValueUpdated = true; if (value != null && !value.equals(viewState.getCurrentValue())) { // 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 // case we just update the value, but not the UI. if (mAutoFilledDataset != null) { final AutofillValue filledValue = findValue(mAutoFilledDataset, id); if (value.equals(filledValue)) { viewState.mAutofillValue = value; viewState.setCurrentValue(value); return; } } // Change value viewState.mAutofillValue = value; viewState.setCurrentValue(value); // Update the chooser UI if (value.isText()) { Loading @@ -545,15 +554,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if ((flags & FLAG_VIEW_ENTERED) != 0) { // Remove the UI if the ViewState has changed. if (mCurrentViewState != viewState) { mUi.hideFillUi(mCurrentViewState != null ? mCurrentViewState.mId : null); mCurrentViewState = viewState; if (mCurrentViewId != viewState.id) { mUi.hideFillUi(mCurrentViewId != null ? mCurrentViewId : null); mCurrentViewId = viewState.id; } // If the ViewState is ready to be displayed, onReady() will be called. viewState.update(value, virtualBounds); // TODO(b/33197203): Remove when there is a response per activity. if (mCurrentResponse != null) { viewState.setResponse(mCurrentResponse); } Loading @@ -562,9 +570,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if ((flags & FLAG_VIEW_EXITED) != 0) { if (mCurrentViewState == viewState) { mUi.hideFillUi(viewState.mId); mCurrentViewState = null; if (mCurrentViewId == viewState.id) { mUi.hideFillUi(viewState.id); mCurrentViewId = null; } return; } Loading @@ -584,17 +592,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } private void notifyUnavailableToClient() { if (mCurrentViewState == null) { if (mCurrentViewId == null) { // TODO(b/33197203): temporary sanity check; should never happen Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null"); Slog.w(TAG, "notifyUnavailable(): mCurrentViewId is null"); return; } if (!mHasCallback) return; try { mClient.notifyNoFillUi(mWindowToken, mCurrentViewState.mId); mClient.notifyNoFillUi(mWindowToken, mCurrentViewId); } catch (RemoteException e) { Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken + " id=" + mCurrentViewState.mId, e); + " id=" + mCurrentViewId, e); } } Loading @@ -604,21 +612,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + "):" + response); } if (mCurrentViewState == null) { if (mCurrentViewId == null) { // TODO(b/33197203): temporary sanity check; should never happen Slog.w(TAG, "processResponseLocked(): mCurrentViewState is null"); Slog.w(TAG, "processResponseLocked(): mCurrentViewId is null"); return; } mCurrentResponse = response; if (mCurrentResponse.getAuthentication() != null) { // Handle authentication. final Intent fillInIntent = createAuthFillInIntent(mStructure, mCurrentResponse.getExtras()); mCurrentViewState.setResponse(mCurrentResponse, fillInIntent); return; } setViewStatesLocked(response, ViewState.STATE_FILLABLE); if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null && response.getDatasets().size() == 1) { Loading @@ -627,7 +629,50 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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) { Loading @@ -650,6 +695,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return mService.getServiceName(); } FillResponse getCurrentResponse() { return mCurrentResponse; } private Intent createAuthFillInIntent(AssistStructure structure, Bundle extras) { final Intent fillInIntent = new Intent(); fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure); Loading @@ -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("mCurrentResponse: "); pw.println(mCurrentResponse); pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset); pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState); pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size()); pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId); pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size()); final String prefix2 = prefix + " "; for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) { pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey()); Loading @@ -700,6 +749,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); } mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues()); setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED); } catch (RemoteException e) { Slog.w(TAG, "Error autofilling activity: " + e); } Loading
services/autofill/java/com/android/server/autofill/ViewState.java +77 −32 Original line number Diff line number Diff line Loading @@ -17,9 +17,9 @@ package com.android.server.autofill; import android.annotation.Nullable; import android.content.Intent; import android.graphics.Rect; import android.service.autofill.FillResponse; import android.util.DebugUtils; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; Loading @@ -40,55 +40,92 @@ final class ViewState { @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; // 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 FillResponse mResponse; private Intent mAuthIntent; // TODO(b/33197203): encapsulate access so it's not called by UI AutofillValue mAutofillValue; private AutofillValue mCurrentValue; private Rect mVirtualBounds; // TODO(b/33197203): encapsulate access so it's not called by UI // Bounds if a virtual view, null otherwise Rect mVirtualBounds; private int mState; boolean mValueUpdated; ViewState(Session session, AutofillId id, Listener listener) { ViewState(Session session, AutofillId id, Listener listener, int state) { mSession = session; mId = id; this.id = id; 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. */ void setResponse(FillResponse response) { mResponse = response; maybeCallOnFillReady(); @Nullable Rect getVirtualBounds() { return mVirtualBounds; } /** * Used when a {@link FillResponse} requires authentication to be unlocked. * Gets the current value of the view. */ void setResponse(FillResponse response, Intent authIntent) { mAuthIntent = authIntent; setResponse(response); @Nullable AutofillValue getCurrentValue() { return mCurrentValue; } void setResponse(FillResponse response) { mResponse = response; } FillResponse getResponse() { return mResponse; } CharSequence 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 // it can change the value and update the UI; similarly, should replace code that // directly sets mAutoFilLValue to use encapsulation. void update(@Nullable AutofillValue autofillValue, @Nullable Rect virtualBounds) { if (autofillValue != null) { mAutofillValue = autofillValue; mCurrentValue = autofillValue; } if (virtualBounds != null) { mVirtualBounds = virtualBounds; Loading @@ -103,23 +140,31 @@ final class ViewState { * fill UI is ready to be displayed (i.e. when response and bounds are set). */ void maybeCallOnFillReady() { if (mResponse != null && (mResponse.getAuthentication() != null || mResponse.getDatasets() != null)) { mListener.onFillReady(mResponse, mId, mAutofillValue); // First try the current response associated with this View. if (mResponse != null) { 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 public String toString() { return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mVirtualBounds + ", updated = " + mValueUpdated + "]"; return "ViewState: [id=" + id + ", currentValue=" + mCurrentValue + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() +"]"; } void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("id:" ); pw.println(mId); pw.print(prefix); pw.print("value:" ); pw.println(mAutofillValue); pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated); pw.print(prefix); pw.print("id:" ); pw.println(this.id); pw.print(prefix); pw.print("state:" ); pw.println(getStateAsString()); 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("authIntent:" ); pw.println(mAuthIntent); } } No newline at end of file