Merge "Initial implementation of autofill partitioning." into oc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
ba2e284a62
@@ -77,11 +77,6 @@ public final class Dataset implements Parcelable {
|
||||
return customPresentation != null ? customPresentation : mPresentation;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public @Nullable RemoteViews getPresentation() {
|
||||
return mPresentation;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public @Nullable IntentSender getAuthentication() {
|
||||
return mAuthentication;
|
||||
|
||||
@@ -290,7 +290,8 @@ final class AutofillManagerServiceImpl {
|
||||
+ " f=" + flags;
|
||||
mRequestsHistory.log(historyItem);
|
||||
|
||||
// TODO(b/33197203): Handle partitioning
|
||||
// TODO(b/33197203): Handle scenario when user forced autofill after app was already
|
||||
// autofilled.
|
||||
final Session session = mSessions.get(activityToken);
|
||||
if (session != null) {
|
||||
// Already started...
|
||||
|
||||
@@ -48,6 +48,7 @@ import android.service.autofill.Dataset;
|
||||
import android.service.autofill.FillResponse;
|
||||
import android.service.autofill.SaveInfo;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.DebugUtils;
|
||||
import android.util.Slog;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.autofill.AutofillManager;
|
||||
@@ -112,20 +113,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
@GuardedBy("mLock")
|
||||
RemoteFillService mRemoteFillService;
|
||||
|
||||
// TODO(b/33197203 , b/35707731): Use List once it supports partitioning
|
||||
@GuardedBy("mLock")
|
||||
private FillResponse mCurrentResponse;
|
||||
private ArrayList<FillResponse> mResponses;
|
||||
|
||||
/**
|
||||
* Used to remember which {@link Dataset} filled the session.
|
||||
* Response that requires a service authentitcation request.
|
||||
*/
|
||||
// TODO(b/33197203 , b/35707731): will be removed once it supports partitioning
|
||||
@GuardedBy("mLock")
|
||||
private Dataset mAutoFilledDataset;
|
||||
private FillResponse mResponseWaitingAuth;
|
||||
|
||||
/**
|
||||
* Dataset that when tapped launched a service authentication request.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private Dataset mDatasetWaitingAuth;
|
||||
|
||||
/**
|
||||
@@ -163,8 +163,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
mClient = IAutoFillManagerClient.Stub.asInterface(client);
|
||||
try {
|
||||
client.linkToDeath(() -> {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "app binder died");
|
||||
if (VERBOSE) {
|
||||
Slog.v(TAG, "app binder died");
|
||||
}
|
||||
|
||||
removeSelf();
|
||||
@@ -193,6 +193,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
notifyUnavailableToClient();
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (response.getAuthentication() != null) {
|
||||
// TODO(b/33197203 , b/35707731): make sure it's ignored if there is one already
|
||||
mResponseWaitingAuth = response;
|
||||
}
|
||||
processResponseLocked(response);
|
||||
}
|
||||
|
||||
@@ -318,23 +322,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
}
|
||||
|
||||
public void setAuthenticationResultLocked(Bundle data) {
|
||||
if (mCurrentResponse == null || data == null) {
|
||||
if ((mResponseWaitingAuth == null && mDatasetWaitingAuth == null) || data == null) {
|
||||
removeSelf();
|
||||
} else {
|
||||
final Parcelable result = data.getParcelable(
|
||||
AutofillManager.EXTRA_AUTHENTICATION_RESULT);
|
||||
if (result instanceof FillResponse) {
|
||||
mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName);
|
||||
|
||||
mCurrentResponse = (FillResponse) result;
|
||||
processResponseLocked(mCurrentResponse);
|
||||
mResponseWaitingAuth = null;
|
||||
processResponseLocked((FillResponse) result);
|
||||
} else if (result instanceof Dataset) {
|
||||
final Dataset dataset = (Dataset) result;
|
||||
final int index = mCurrentResponse.getDatasets().indexOf(mDatasetWaitingAuth);
|
||||
if (index >= 0) {
|
||||
mCurrentResponse.getDatasets().set(index, dataset);
|
||||
autoFill(dataset);
|
||||
mDatasetWaitingAuth = null;
|
||||
for (int i = 0; i < mResponses.size(); i++) {
|
||||
final FillResponse response = mResponses.get(i);
|
||||
final int index = response.getDatasets().indexOf(mDatasetWaitingAuth);
|
||||
if (index >= 0) {
|
||||
response.getDatasets().set(index, dataset);
|
||||
mDatasetWaitingAuth = null;
|
||||
autoFill(dataset);
|
||||
resetViewStatesLocked(dataset, ViewState.STATE_WAITING_DATASET_AUTH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,15 +362,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
Slog.wtf(TAG, "showSaveLocked(): no mStructure");
|
||||
return true;
|
||||
}
|
||||
if (mCurrentResponse == null) {
|
||||
if (mResponses == null) {
|
||||
// Happens when the activity / session was finished before the service replied, or
|
||||
// when the service cannot autofill it (and returned a null response).
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "showSaveLocked(): no mCurrentResponse");
|
||||
Slog.d(TAG, "showSaveLocked(): no responses on session");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
final SaveInfo saveInfo = mCurrentResponse.getSaveInfo();
|
||||
|
||||
// TODO(b/33197203 , b/35707731): must iterate over all responses
|
||||
final FillResponse response = mResponses.get(0);
|
||||
|
||||
final SaveInfo saveInfo = response.getSaveInfo();
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo);
|
||||
}
|
||||
@@ -385,7 +397,6 @@ 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++) {
|
||||
@@ -393,7 +404,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
final ViewState viewState = mViewStates.get(id);
|
||||
if (viewState == null) {
|
||||
Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id);
|
||||
continue;
|
||||
allRequiredAreNotEmpty = false;
|
||||
break;
|
||||
}
|
||||
|
||||
final AutofillValue currentValue = viewState.getCurrentValue();
|
||||
@@ -462,7 +474,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
|
||||
}
|
||||
|
||||
final Bundle extras = this.mCurrentResponse.getExtras();
|
||||
// TODO(b/33197203 , b/35707731): decide how to handle bundle in multiple partitions
|
||||
final Bundle extras = mResponses != null ? mResponses.get(0).getExtras() : null;
|
||||
|
||||
for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
|
||||
final AutofillValue value = entry.getValue().getCurrentValue();
|
||||
@@ -497,16 +510,21 @@ 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 " + id + " after app was autofilled");
|
||||
return;
|
||||
}
|
||||
|
||||
ViewState viewState = mViewStates.get(id);
|
||||
|
||||
if (viewState == null) {
|
||||
viewState = new ViewState(this, id, this, ViewState.STATE_INITIAL);
|
||||
mViewStates.put(id, viewState);
|
||||
if ((flags & (FLAG_START_SESSION | FLAG_VALUE_CHANGED)) != 0) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Creating viewState for " + id + " on " + getFlagAsString(flags));
|
||||
}
|
||||
viewState = new ViewState(this, id, this, ViewState.STATE_INITIAL);
|
||||
mViewStates.put(id, viewState);
|
||||
} else if ((flags & FLAG_VIEW_ENTERED) != 0) {
|
||||
viewState = startPartitionLocked(id);
|
||||
} else {
|
||||
if (VERBOSE) Slog.v(TAG, "Ignored " + getFlagAsString(flags) + " for " + id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & FLAG_START_SESSION) != 0) {
|
||||
@@ -530,7 +548,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
}
|
||||
// Update the internal state...
|
||||
viewState.setState(ViewState.STATE_CHANGED);
|
||||
// ... and the chooser UI.
|
||||
|
||||
//..and the UI
|
||||
if (value.isText()) {
|
||||
getUiForShowing().filterFillUi(value.getTextValue().toString());
|
||||
} else {
|
||||
@@ -551,10 +570,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
// If the ViewState is ready to be displayed, onReady() will be called.
|
||||
viewState.update(value, virtualBounds);
|
||||
|
||||
if (mCurrentResponse != null) {
|
||||
viewState.setResponse(mCurrentResponse);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -566,7 +581,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
return;
|
||||
}
|
||||
|
||||
Slog.w(TAG, "updateLocked(): unknown flags " + flags);
|
||||
Slog.w(TAG, "updateLocked(): unknown flags " + flags + ": " + getFlagAsString(flags));
|
||||
}
|
||||
|
||||
private ViewState startPartitionLocked(AutofillId id) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Starting partition for view id " + id);
|
||||
}
|
||||
final ViewState viewState =
|
||||
new ViewState(this, id, this,ViewState.STATE_STARTED_PARTITION);
|
||||
mViewStates.put(id, viewState);
|
||||
|
||||
/*
|
||||
* TODO(b/33197203 , b/35707731): when start a new partition, it should
|
||||
*
|
||||
* - add autofilled fields as sanitized
|
||||
* - set focus on ViewStructure that triggered it
|
||||
* - pass the first onFillRequest() bundle
|
||||
* - optional: perhaps add a new flag onFilLRequest() to indicate it's a new partition?
|
||||
*/
|
||||
mRemoteFillService.onFillRequest(mStructure, null, 0);
|
||||
|
||||
return viewState;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -580,6 +616,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
getUiForShowing().showFillUi(filledId, response, filterText, mPackageName);
|
||||
}
|
||||
|
||||
String getFlagAsString(int flag) {
|
||||
return DebugUtils.flagsToString(AutofillManager.class, "FLAG_", flag);
|
||||
}
|
||||
|
||||
private void notifyUnavailableToClient() {
|
||||
if (mCurrentViewId == null) {
|
||||
// TODO(b/33197203): temporary sanity check; should never happen
|
||||
@@ -597,8 +637,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
|
||||
private void processResponseLocked(FillResponse response) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "processResponseLocked(auth=" + response.getAuthentication()
|
||||
+ "):" + response);
|
||||
Slog.d(TAG, "processResponseLocked(mCurrentViewId=" + mCurrentViewId + "):" + response);
|
||||
}
|
||||
|
||||
if (mCurrentViewId == null) {
|
||||
@@ -607,7 +646,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrentResponse = response;
|
||||
if (mResponses == null) {
|
||||
mResponses = new ArrayList<>(4);
|
||||
}
|
||||
mResponses.add(response);
|
||||
|
||||
setViewStatesLocked(response, ViewState.STATE_FILLABLE);
|
||||
|
||||
@@ -669,10 +711,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the given state from all existing views in the given dataset.
|
||||
*/
|
||||
private void resetViewStatesLocked(@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);
|
||||
final ViewState viewState = mViewStates.get(id);
|
||||
if (viewState != null) {
|
||||
viewState.resetState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void autoFill(Dataset dataset) {
|
||||
synchronized (mLock) {
|
||||
mAutoFilledDataset = dataset;
|
||||
|
||||
// Autofill it directly...
|
||||
if (dataset.getAuthentication() == null) {
|
||||
autoFillApp(dataset);
|
||||
@@ -680,7 +734,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
}
|
||||
|
||||
// ...or handle authentication.
|
||||
// TODO(b/33197203 , b/35707731): make sure it's ignored if there is one already
|
||||
mDatasetWaitingAuth = dataset;
|
||||
setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH);
|
||||
final Intent fillInIntent = createAuthFillInIntent(mStructure, null);
|
||||
startAuthentication(dataset.getAuthentication(), fillInIntent);
|
||||
}
|
||||
@@ -690,8 +746,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
return mService.getServiceName();
|
||||
}
|
||||
|
||||
FillResponse getCurrentResponse() {
|
||||
return mCurrentResponse;
|
||||
FillResponse getResponseWaitingAuth() {
|
||||
return mResponseWaitingAuth;
|
||||
}
|
||||
|
||||
private Intent createAuthFillInIntent(AssistStructure structure, Bundle extras) {
|
||||
@@ -714,8 +770,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
void dumpLocked(String prefix, PrintWriter pw) {
|
||||
pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
|
||||
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("mResponses: "); pw.println(mResponses);
|
||||
pw.print(prefix); pw.print("mResponseWaitingAuth: "); pw.println(mResponseWaitingAuth);
|
||||
pw.print(prefix); pw.print("mDatasetWaitingAuth: "); pw.println(mDatasetWaitingAuth);
|
||||
pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId);
|
||||
pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
|
||||
@@ -811,4 +867,4 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
destroyLocked();
|
||||
mService.removeSessionLocked(mActivityToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package com.android.server.autofill;
|
||||
|
||||
import static com.android.server.autofill.Helper.DEBUG;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.Rect;
|
||||
import android.service.autofill.FillResponse;
|
||||
import android.util.DebugUtils;
|
||||
import android.util.Slog;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.autofill.AutofillValue;
|
||||
|
||||
@@ -40,6 +43,8 @@ final class ViewState {
|
||||
@Nullable AutofillValue value);
|
||||
}
|
||||
|
||||
private static final String TAG = "ViewState";
|
||||
|
||||
// NOTE: state constants must be public because of flagstoString().
|
||||
public static final int STATE_UNKNOWN = 0x00;
|
||||
/** Initial state. */
|
||||
@@ -52,6 +57,10 @@ final class ViewState {
|
||||
public static final int STATE_CHANGED = 0x08;
|
||||
/** Set only in the View that started a session. */
|
||||
public static final int STATE_STARTED_SESSION = 0x10;
|
||||
/** View that started a new partition when focused on. */
|
||||
public static final int STATE_STARTED_PARTITION = 0x20;
|
||||
/** User select a dataset in this view, but service must authenticate first. */
|
||||
public static final int STATE_WAITING_DATASET_AUTH = 0x40;
|
||||
|
||||
public final AutofillId id;
|
||||
private final Listener mListener;
|
||||
@@ -122,9 +131,15 @@ final class ViewState {
|
||||
}
|
||||
|
||||
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;
|
||||
if (mState == STATE_INITIAL) {
|
||||
mState = state;
|
||||
} else {
|
||||
mState |= state;
|
||||
}
|
||||
}
|
||||
|
||||
void resetState(int state) {
|
||||
mState &= ~state;
|
||||
}
|
||||
|
||||
// TODO(b/33197203): need to refactor / rename / document this method to make it clear that
|
||||
@@ -147,6 +162,12 @@ final class ViewState {
|
||||
* fill UI is ready to be displayed (i.e. when response and bounds are set).
|
||||
*/
|
||||
void maybeCallOnFillReady() {
|
||||
if ((mState & (STATE_AUTOFILLED | STATE_WAITING_DATASET_AUTH)) != 0) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Ignoring UI for " + id + " on " + getStateAsString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
// First try the current response associated with this View.
|
||||
if (mResponse != null) {
|
||||
if (mResponse.getDatasets() != null) {
|
||||
@@ -155,9 +176,9 @@ final class ViewState {
|
||||
return;
|
||||
}
|
||||
// Then checks if the session has a response waiting authentication; if so, uses it instead.
|
||||
final FillResponse currentResponse = mSession.getCurrentResponse();
|
||||
if (currentResponse != null && currentResponse.getAuthentication() != null) {
|
||||
mListener.onFillReady(currentResponse, this.id, mCurrentValue);
|
||||
final FillResponse responseWaitingAuth = mSession.getResponseWaitingAuth();
|
||||
if (responseWaitingAuth != null) {
|
||||
mListener.onFillReady(responseWaitingAuth, this.id, mCurrentValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user