[Autofill PCC Integration] : Implement core logic
This CL establishes connection to the remote classification service, and uses it's results to merge with provider results. Test: To be added in a follow-up cl. Tested AiAi connection, and it works. Change-Id: I6311269e5cc078b3739f4bfab73c9ae66670177b
This commit is contained in:
@@ -18,6 +18,7 @@ package android.service.autofill;
|
||||
|
||||
import static android.view.autofill.Helper.sDebug;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SuppressLint;
|
||||
@@ -33,6 +34,8 @@ import android.widget.RemoteViews;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -112,6 +115,55 @@ import java.util.regex.Pattern;
|
||||
* </ol>
|
||||
*/
|
||||
public final class Dataset implements Parcelable {
|
||||
/**
|
||||
* This dataset is picked because of unknown reason.
|
||||
* @hide
|
||||
*/
|
||||
public static final int PICK_REASON_UNKNOWN = 0;
|
||||
/**
|
||||
* This dataset is picked because of autofill provider detection was chosen.
|
||||
* @hide
|
||||
*/
|
||||
public static final int PICK_REASON_AUTOFILL_PROVIDER_DETECTION = 1;
|
||||
/**
|
||||
* This dataset is picked because of PCC detection was chosen.
|
||||
* @hide
|
||||
*/
|
||||
public static final int PICK_REASON_PCC_DETECTION = 2;
|
||||
/**
|
||||
* This dataset is picked because of Framework detection was chosen.
|
||||
* @hide
|
||||
*/
|
||||
public static final int PICK_REASON_FRAMEWORK_DETECTION = 3;
|
||||
/**
|
||||
* This dataset is picked because of Autofill Provider being a fallback.
|
||||
* @hide
|
||||
*/
|
||||
public static final int PICK_REASON_AUTOFILL_PROVIDER_FALLBACK = 4;
|
||||
/**
|
||||
* This dataset is picked because of PCC detection being a fallback.
|
||||
* @hide
|
||||
*/
|
||||
public static final int PICK_REASON_PCC_DETECTION_FALLBACK = 5;
|
||||
/**
|
||||
* This dataset is picked because of Framework detection being a fallback.
|
||||
* @hide
|
||||
*/
|
||||
public static final int PICK_REASON_FRAMEWORK_FALLBACK = 6;
|
||||
|
||||
@IntDef(prefix = { "PICK_REASON_" }, value = {
|
||||
PICK_REASON_UNKNOWN,
|
||||
PICK_REASON_AUTOFILL_PROVIDER_DETECTION,
|
||||
PICK_REASON_PCC_DETECTION,
|
||||
PICK_REASON_FRAMEWORK_DETECTION,
|
||||
PICK_REASON_AUTOFILL_PROVIDER_FALLBACK,
|
||||
PICK_REASON_PCC_DETECTION_FALLBACK,
|
||||
PICK_REASON_FRAMEWORK_FALLBACK,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface DatasetEligibleReason{}
|
||||
|
||||
private @DatasetEligibleReason int mEligibleReason;
|
||||
|
||||
private final ArrayList<AutofillId> mFieldIds;
|
||||
private final ArrayList<AutofillValue> mFieldValues;
|
||||
@@ -130,6 +182,67 @@ public final class Dataset implements Parcelable {
|
||||
private final IntentSender mAuthentication;
|
||||
@Nullable String mId;
|
||||
|
||||
/**
|
||||
* Constructor to copy the dataset, but replaces the AutofillId with the given input.
|
||||
* Useful to modify the field type, and provide autofillId.
|
||||
* @hide
|
||||
*/
|
||||
public Dataset(
|
||||
ArrayList<AutofillId> fieldIds,
|
||||
ArrayList<AutofillValue> fieldValues,
|
||||
ArrayList<RemoteViews> fieldPresentations,
|
||||
ArrayList<RemoteViews> fieldDialogPresentations,
|
||||
ArrayList<InlinePresentation> fieldInlinePresentations,
|
||||
ArrayList<InlinePresentation> fieldInlineTooltipPresentations,
|
||||
ArrayList<DatasetFieldFilter> fieldFilters,
|
||||
ArrayList<String> autofillDatatypes,
|
||||
ClipData fieldContent,
|
||||
RemoteViews presentation,
|
||||
RemoteViews dialogPresentation,
|
||||
@Nullable InlinePresentation inlinePresentation,
|
||||
@Nullable InlinePresentation inlineTooltipPresentation,
|
||||
@Nullable String id,
|
||||
IntentSender authentication) {
|
||||
mFieldIds = fieldIds;
|
||||
mFieldValues = fieldValues;
|
||||
mFieldPresentations = fieldPresentations;
|
||||
mFieldDialogPresentations = fieldDialogPresentations;
|
||||
mFieldInlinePresentations = fieldInlinePresentations;
|
||||
mFieldInlineTooltipPresentations = fieldInlineTooltipPresentations;
|
||||
mAutofillDatatypes = autofillDatatypes;
|
||||
mFieldFilters = fieldFilters;
|
||||
mFieldContent = fieldContent;
|
||||
mPresentation = presentation;
|
||||
mDialogPresentation = dialogPresentation;
|
||||
mInlinePresentation = inlinePresentation;
|
||||
mInlineTooltipPresentation = inlineTooltipPresentation;
|
||||
mAuthentication = authentication;
|
||||
mId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor to copy the dataset, but replaces the AutofillId with the given input.
|
||||
* Useful to modify the field type, and provide autofillId.
|
||||
* @hide
|
||||
*/
|
||||
public Dataset(Dataset dataset, ArrayList<AutofillId> ids) {
|
||||
mFieldIds = ids;
|
||||
mFieldValues = dataset.mFieldValues;
|
||||
mFieldPresentations = dataset.mFieldPresentations;
|
||||
mFieldDialogPresentations = dataset.mFieldDialogPresentations;
|
||||
mFieldInlinePresentations = dataset.mFieldInlinePresentations;
|
||||
mFieldInlineTooltipPresentations = dataset.mFieldInlineTooltipPresentations;
|
||||
mFieldFilters = dataset.mFieldFilters;
|
||||
mFieldContent = dataset.mFieldContent;
|
||||
mPresentation = dataset.mPresentation;
|
||||
mDialogPresentation = dataset.mDialogPresentation;
|
||||
mInlinePresentation = dataset.mInlinePresentation;
|
||||
mInlineTooltipPresentation = dataset.mInlineTooltipPresentation;
|
||||
mAuthentication = dataset.mAuthentication;
|
||||
mId = dataset.mId;
|
||||
mAutofillDatatypes = dataset.mAutofillDatatypes;
|
||||
}
|
||||
|
||||
private Dataset(Builder builder) {
|
||||
mFieldIds = builder.mFieldIds;
|
||||
mFieldValues = builder.mFieldValues;
|
||||
@@ -291,6 +404,22 @@ public final class Dataset implements Parcelable {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reason as to why this dataset is eligible
|
||||
* @hide
|
||||
*/
|
||||
public void setEligibleReasonReason(@DatasetEligibleReason int eligibleReason) {
|
||||
this.mEligibleReason = eligibleReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reason as to why this dataset is eligible.
|
||||
* @hide
|
||||
*/
|
||||
public @DatasetEligibleReason int getEligibleReason() {
|
||||
return mEligibleReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link Dataset} objects. You must provide at least
|
||||
* one value for a field or set an authentication intent.
|
||||
@@ -1147,6 +1276,7 @@ public final class Dataset implements Parcelable {
|
||||
parcel.writeParcelable(mFieldContent, flags);
|
||||
parcel.writeParcelable(mAuthentication, flags);
|
||||
parcel.writeString(mId);
|
||||
parcel.writeInt(mEligibleReason);
|
||||
}
|
||||
|
||||
public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
|
||||
@@ -1181,6 +1311,7 @@ public final class Dataset implements Parcelable {
|
||||
final IntentSender authentication = parcel.readParcelable(null,
|
||||
android.content.IntentSender.class);
|
||||
final String datasetId = parcel.readString();
|
||||
final int eligibleReason = parcel.readInt();
|
||||
|
||||
// Always go through the builder to ensure the data ingested by
|
||||
// the system obeys the contract of the builder to avoid attacks
|
||||
@@ -1243,7 +1374,9 @@ public final class Dataset implements Parcelable {
|
||||
}
|
||||
builder.setAuthentication(authentication);
|
||||
builder.setId(datasetId);
|
||||
return builder.build();
|
||||
Dataset dataset = builder.build();
|
||||
dataset.mEligibleReason = eligibleReason;
|
||||
return dataset;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -117,6 +117,80 @@ public final class FillResponse implements Parcelable {
|
||||
private final boolean mShowSaveDialogIcon;
|
||||
private final @Nullable FieldClassification[] mDetectedFieldTypes;
|
||||
|
||||
/**
|
||||
* Creates a shollow copy of the provided FillResponse.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static FillResponse shallowCopy(FillResponse r, List<Dataset> datasets) {
|
||||
return new FillResponse(
|
||||
(datasets != null) ? new ParceledListSlice<>(datasets) : null,
|
||||
r.mSaveInfo,
|
||||
r.mClientState,
|
||||
r.mPresentation,
|
||||
r.mInlinePresentation,
|
||||
r.mInlineTooltipPresentation,
|
||||
r.mDialogPresentation,
|
||||
r.mDialogHeader,
|
||||
r.mHeader,
|
||||
r.mFooter,
|
||||
r.mAuthentication,
|
||||
r.mAuthenticationIds,
|
||||
r.mIgnoredIds,
|
||||
r.mFillDialogTriggerIds,
|
||||
r.mDisableDuration,
|
||||
r.mFieldClassificationIds,
|
||||
r.mFlags,
|
||||
r.mRequestId,
|
||||
r.mUserData,
|
||||
r.mCancelIds,
|
||||
r.mSupportsInlineSuggestions,
|
||||
r.mIconResourceId,
|
||||
r.mServiceDisplayNameResourceId,
|
||||
r.mShowFillDialogIcon,
|
||||
r.mShowSaveDialogIcon,
|
||||
r.mDetectedFieldTypes);
|
||||
}
|
||||
|
||||
private FillResponse(ParceledListSlice<Dataset> datasets, SaveInfo saveInfo, Bundle clientState,
|
||||
RemoteViews presentation, InlinePresentation inlinePresentation,
|
||||
InlinePresentation inlineTooltipPresentation, RemoteViews dialogPresentation,
|
||||
RemoteViews dialogHeader, RemoteViews header, RemoteViews footer,
|
||||
IntentSender authentication, AutofillId[] authenticationIds, AutofillId[] ignoredIds,
|
||||
AutofillId[] fillDialogTriggerIds, long disableDuration,
|
||||
AutofillId[] fieldClassificationIds, int flags, int requestId, UserData userData,
|
||||
int[] cancelIds, boolean supportsInlineSuggestions, int iconResourceId,
|
||||
int serviceDisplayNameResourceId, boolean showFillDialogIcon,
|
||||
boolean showSaveDialogIcon,
|
||||
FieldClassification[] detectedFieldTypes) {
|
||||
mDatasets = datasets;
|
||||
mSaveInfo = saveInfo;
|
||||
mClientState = clientState;
|
||||
mPresentation = presentation;
|
||||
mInlinePresentation = inlinePresentation;
|
||||
mInlineTooltipPresentation = inlineTooltipPresentation;
|
||||
mDialogPresentation = dialogPresentation;
|
||||
mDialogHeader = dialogHeader;
|
||||
mHeader = header;
|
||||
mFooter = footer;
|
||||
mAuthentication = authentication;
|
||||
mAuthenticationIds = authenticationIds;
|
||||
mIgnoredIds = ignoredIds;
|
||||
mFillDialogTriggerIds = fillDialogTriggerIds;
|
||||
mDisableDuration = disableDuration;
|
||||
mFieldClassificationIds = fieldClassificationIds;
|
||||
mFlags = flags;
|
||||
mRequestId = requestId;
|
||||
mUserData = userData;
|
||||
mCancelIds = cancelIds;
|
||||
mSupportsInlineSuggestions = supportsInlineSuggestions;
|
||||
mIconResourceId = iconResourceId;
|
||||
mServiceDisplayNameResourceId = serviceDisplayNameResourceId;
|
||||
mShowFillDialogIcon = showFillDialogIcon;
|
||||
mShowSaveDialogIcon = showSaveDialogIcon;
|
||||
mDetectedFieldTypes = detectedFieldTypes;
|
||||
}
|
||||
|
||||
private FillResponse(@NonNull Builder builder) {
|
||||
mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
|
||||
mSaveInfo = builder.mSaveInfo;
|
||||
@@ -673,6 +747,15 @@ public final class FillResponse implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setDatasets(ArrayList<Dataset> dataset) {
|
||||
mDatasets = dataset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link SaveInfo} associated with this response.
|
||||
*
|
||||
|
||||
@@ -81,12 +81,6 @@ public class AutofillFeatureFlags {
|
||||
public static final String DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED =
|
||||
"autofill_dialog_enabled";
|
||||
|
||||
/**
|
||||
* Indicates that PCC Autofill detection feature is enabled or not.
|
||||
*/
|
||||
public static final String DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS =
|
||||
"pcc_classification_hints";
|
||||
|
||||
/**
|
||||
* Sets the autofill hints allowed list for the fields that can trigger the fill dialog
|
||||
* feature at Activity starting.
|
||||
@@ -190,6 +184,12 @@ public class AutofillFeatureFlags {
|
||||
*/
|
||||
public static final String DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC = "prefer_provider_over_pcc";
|
||||
|
||||
/**
|
||||
* Indicates the Autofill Hints that would be requested by the service from the Autofill
|
||||
* Provider.
|
||||
*/
|
||||
public static final String DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS =
|
||||
"pcc_classification_hints";
|
||||
|
||||
/**
|
||||
* Use data from secondary source if primary not present .
|
||||
@@ -212,11 +212,9 @@ public class AutofillFeatureFlags {
|
||||
"autofill_inline_tooltip_first_show_delay";
|
||||
|
||||
private static final String DIALOG_HINTS_DELIMITER = ":";
|
||||
private static final String PCC_HINTS_DELIMITER = ",";
|
||||
|
||||
private static final boolean DEFAULT_HAS_FILL_DIALOG_UI_FEATURE = false;
|
||||
private static final String DEFAULT_FILL_DIALOG_ENABLED_HINTS = "";
|
||||
private static final String DEFAULT_PCC_FEATURE_PROVIDER_HINTS = "";
|
||||
|
||||
|
||||
// CREDENTIAL MANAGER DEFAULTS
|
||||
@@ -249,25 +247,6 @@ public class AutofillFeatureFlags {
|
||||
DEFAULT_HAS_FILL_DIALOG_UI_FEATURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of datatypes that is supported by framework
|
||||
* detection.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static String[] getTypeHintsForProvider() {
|
||||
final String typeHints = DeviceConfig.getString(
|
||||
DeviceConfig.NAMESPACE_AUTOFILL,
|
||||
DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS,
|
||||
DEFAULT_PCC_FEATURE_PROVIDER_HINTS);
|
||||
if (TextUtils.isEmpty(typeHints)) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
return ArrayUtils.filter(typeHints.split(PCC_HINTS_DELIMITER), String[]::new,
|
||||
(str) -> !TextUtils.isEmpty(str));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets fill dialog enabled hints.
|
||||
*
|
||||
|
||||
@@ -150,6 +150,12 @@ public final class AutofillManagerService
|
||||
@NonNull
|
||||
final FrameworkResourcesServiceNameResolver mAugmentedAutofillResolver;
|
||||
|
||||
/**
|
||||
* Object used to set the name of the field classification service.
|
||||
*/
|
||||
@NonNull
|
||||
final FrameworkResourcesServiceNameResolver mFieldClassificationResolver;
|
||||
|
||||
private final AutoFillUI mUi;
|
||||
|
||||
private final LocalLog mRequestsHistory = new LocalLog(20);
|
||||
@@ -245,6 +251,15 @@ public final class AutofillManagerService
|
||||
mAugmentedAutofillResolver.setOnTemporaryServiceNameChangedCallback(
|
||||
(u, s, t) -> onAugmentedServiceNameChanged(u, s, t));
|
||||
|
||||
mFieldClassificationResolver = new FrameworkResourcesServiceNameResolver(getContext(),
|
||||
com.android.internal.R.string.config_defaultFieldClassificationService);
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG, "Resolving FieldClassificationService to serviceName: "
|
||||
+ mFieldClassificationResolver.readServiceName(0));
|
||||
}
|
||||
mFieldClassificationResolver.setOnTemporaryServiceNameChangedCallback(
|
||||
(u, s, t) -> onFieldClassificationServiceNameChanged(u, s, t));
|
||||
|
||||
if (mSupportedSmartSuggestionModes != AutofillManager.FLAG_SMART_SUGGESTION_OFF) {
|
||||
final List<UserInfo> users = getSupportedUsers();
|
||||
for (int i = 0; i < users.size(); i++) {
|
||||
@@ -358,6 +373,20 @@ public final class AutofillManagerService
|
||||
}
|
||||
}
|
||||
|
||||
private void onFieldClassificationServiceNameChanged(
|
||||
@UserIdInt int userId, @Nullable String serviceName, boolean isTemporary) {
|
||||
synchronized (mLock) {
|
||||
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
|
||||
if (service == null) {
|
||||
// If we cannot get the service from the services cache, it will call
|
||||
// updateRemoteAugmentedAutofillService() finally. Skip call this update again.
|
||||
getServiceForUserLocked(userId);
|
||||
} else {
|
||||
service.updateRemoteFieldClassificationService();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from AbstractMasterSystemService
|
||||
protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
|
||||
boolean disabled) {
|
||||
|
||||
@@ -161,6 +161,17 @@ final class AutofillManagerServiceImpl
|
||||
/** When was {@link PruneTask} last executed? */
|
||||
private long mLastPrune = 0;
|
||||
|
||||
/**
|
||||
* Reference to the {@link RemoteFieldClassificationService}, is set on demand.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
private RemoteFieldClassificationService mRemoteFieldClassificationService;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
private ServiceInfo mRemoteFieldClassificationServiceInfo;
|
||||
|
||||
/**
|
||||
* Reference to the {@link RemoteAugmentedAutofillService}, is set on demand.
|
||||
*/
|
||||
@@ -1051,10 +1062,11 @@ final class AutofillManagerServiceImpl
|
||||
}
|
||||
pw.print(prefix); pw.print("Default component: "); pw.println(getContext()
|
||||
.getString(R.string.config_defaultAutofillService));
|
||||
pw.println();
|
||||
|
||||
pw.print(prefix); pw.println("mAugmentedAutofillNamer: ");
|
||||
pw.print(prefix2); mMaster.mAugmentedAutofillResolver.dumpShort(pw, mUserId); pw.println();
|
||||
|
||||
pw.print(prefix); pw.println("mAugmentedAutofillName: ");
|
||||
pw.print(prefix2); mMaster.mAugmentedAutofillResolver.dumpShort(pw, mUserId);
|
||||
pw.println();
|
||||
if (mRemoteAugmentedAutofillService != null) {
|
||||
pw.print(prefix); pw.println("RemoteAugmentedAutofillService: ");
|
||||
mRemoteAugmentedAutofillService.dump(prefix2, pw);
|
||||
@@ -1063,6 +1075,27 @@ final class AutofillManagerServiceImpl
|
||||
pw.print(prefix); pw.print("RemoteAugmentedAutofillServiceInfo: ");
|
||||
pw.println(mRemoteAugmentedAutofillServiceInfo);
|
||||
}
|
||||
pw.println();
|
||||
|
||||
pw.print(prefix); pw.println("mFieldClassificationService for system detection");
|
||||
pw.print(prefix2); pw.print("Default component: "); pw.println(getContext()
|
||||
.getString(R.string.config_defaultFieldClassificationService));
|
||||
pw.print(prefix2); mMaster.mFieldClassificationResolver.dumpShort(pw, mUserId);
|
||||
pw.println();
|
||||
|
||||
if (mRemoteFieldClassificationService != null) {
|
||||
pw.print(prefix); pw.println("RemoteFieldClassificationService: ");
|
||||
mRemoteFieldClassificationService.dump(prefix2, pw);
|
||||
} else {
|
||||
pw.print(prefix); pw.println("mRemoteFieldClassificationService: null");
|
||||
}
|
||||
if (mRemoteFieldClassificationServiceInfo != null) {
|
||||
pw.print(prefix); pw.print("RemoteFieldClassificationServiceInfo: ");
|
||||
pw.println(mRemoteFieldClassificationServiceInfo);
|
||||
} else {
|
||||
pw.print(prefix); pw.println("mRemoteFieldClassificationServiceInfo: null");
|
||||
}
|
||||
pw.println();
|
||||
|
||||
pw.print(prefix); pw.print("Field classification enabled: ");
|
||||
pw.println(isFieldClassificationEnabledLocked());
|
||||
@@ -1629,6 +1662,95 @@ final class AutofillManagerServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Nullable RemoteFieldClassificationService getRemoteFieldClassificationServiceLocked() {
|
||||
if (mRemoteFieldClassificationService == null) {
|
||||
final String serviceName = mMaster.mFieldClassificationResolver.getServiceName(mUserId);
|
||||
if (serviceName == null) {
|
||||
if (mMaster.verbose) {
|
||||
Slog.v(TAG, "getRemoteFieldClassificationServiceLocked(): not set");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG, "getRemoteFieldClassificationServiceLocked serviceName: "
|
||||
+ serviceName);
|
||||
}
|
||||
boolean sTemporaryFieldDetectionService =
|
||||
mMaster.mFieldClassificationResolver.isTemporary(mUserId);
|
||||
final Pair<ServiceInfo, ComponentName> pair = RemoteFieldClassificationService
|
||||
.getComponentName(serviceName, mUserId, sTemporaryFieldDetectionService);
|
||||
if (pair == null) {
|
||||
Slog.w(TAG, "RemoteFieldClassificationService.getComponentName returned null "
|
||||
+ "with serviceName: " + serviceName);
|
||||
return null;
|
||||
}
|
||||
|
||||
mRemoteFieldClassificationServiceInfo = pair.first;
|
||||
final ComponentName componentName = pair.second;
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG, "getRemoteFieldClassificationServiceLocked(): " + componentName);
|
||||
}
|
||||
final int serviceUid = mRemoteFieldClassificationServiceInfo.applicationInfo.uid;
|
||||
mRemoteFieldClassificationService = new RemoteFieldClassificationService(getContext(),
|
||||
componentName, serviceUid, mUserId);
|
||||
}
|
||||
|
||||
return mRemoteFieldClassificationService;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Nullable RemoteFieldClassificationService
|
||||
getRemoteFieldClassificationServiceIfCreatedLocked() {
|
||||
return mRemoteFieldClassificationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the {@link AutofillManagerService#mAugmentedAutofillResolver}
|
||||
* changed (among other places).
|
||||
*/
|
||||
void updateRemoteFieldClassificationService() {
|
||||
synchronized (mLock) {
|
||||
if (mRemoteFieldClassificationService != null) {
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG, "updateRemoteFieldClassificationService(): "
|
||||
+ "destroying old remote service");
|
||||
}
|
||||
mRemoteFieldClassificationService.unbind();
|
||||
|
||||
mRemoteFieldClassificationService = null;
|
||||
mRemoteFieldClassificationServiceInfo = null;
|
||||
}
|
||||
|
||||
final boolean available = isFieldClassificationServiceAvailableLocked();
|
||||
if (sVerbose) Slog.v(TAG, "updateRemoteFieldClassificationService(): " + available);
|
||||
|
||||
if (available) {
|
||||
mRemoteFieldClassificationService = getRemoteFieldClassificationServiceLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFieldClassificationServiceAvailableLocked() {
|
||||
if (mMaster.verbose) {
|
||||
Slog.v(TAG, "isAugmentedAutofillService(): "
|
||||
+ "setupCompleted=" + isSetupCompletedLocked()
|
||||
+ ", disabled=" + isDisabledByUserRestrictionsLocked()
|
||||
+ ", augmentedService="
|
||||
+ mMaster.mAugmentedAutofillResolver.getServiceName(mUserId));
|
||||
}
|
||||
if (!isSetupCompletedLocked() || isDisabledByUserRestrictionsLocked()
|
||||
|| mMaster.mAugmentedAutofillResolver.getServiceName(mUserId) == null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isRemoteClassificationServiceForUserLocked(int callingUid) {
|
||||
return mRemoteFieldClassificationServiceInfo != null
|
||||
&& mRemoteFieldClassificationServiceInfo.applicationInfo.uid == callingUid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AutofillManagerServiceImpl: [userId=" + mUserId
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.autofill;
|
||||
|
||||
import static com.android.server.autofill.Helper.sDebug;
|
||||
import static com.android.server.autofill.Helper.sVerbose;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.AppGlobals;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.ICancellationSignal;
|
||||
import android.os.RemoteException;
|
||||
import android.service.assist.classification.FieldClassificationRequest;
|
||||
import android.service.assist.classification.FieldClassificationResponse;
|
||||
import android.service.assist.classification.FieldClassificationService;
|
||||
import android.service.assist.classification.IFieldClassificationCallback;
|
||||
import android.service.assist.classification.IFieldClassificationService;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.infra.AbstractRemoteService;
|
||||
import com.android.internal.infra.ServiceConnector;
|
||||
|
||||
/**
|
||||
* Class responsible for connection with the Remote {@link FieldClassificationService}.
|
||||
* This class is instantiated when {@link AutofillManagerServiceImpl} is established.
|
||||
* The connection is supposed to be bounded forever, as such, this class persists beyond
|
||||
* Autofill {@link Session}'s lifecycle. As such, it can't contain information relevant to Session.
|
||||
* This design is completely different from {@link RemoteFillService}.
|
||||
*/
|
||||
final class RemoteFieldClassificationService
|
||||
extends ServiceConnector.Impl<IFieldClassificationService> {
|
||||
|
||||
private static final String TAG =
|
||||
"Autofill" + RemoteFieldClassificationService.class.getSimpleName();
|
||||
|
||||
// Bind forever.
|
||||
private static final long TIMEOUT_IDLE_UNBIND_MS =
|
||||
AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
|
||||
private final ComponentName mComponentName;
|
||||
|
||||
public interface FieldClassificationServiceCallbacks {
|
||||
void onClassificationRequestSuccess(@NonNull FieldClassificationResponse response);
|
||||
void onClassificationRequestFailure(int requestId, @Nullable CharSequence message);
|
||||
void onClassificationRequestTimeout(int requestId);
|
||||
void onServiceDied(@NonNull RemoteFieldClassificationService service);
|
||||
}
|
||||
|
||||
RemoteFieldClassificationService(Context context, ComponentName serviceName,
|
||||
int serviceUid, int userId) {
|
||||
super(context,
|
||||
// TODO(b/266379948): Update service
|
||||
new Intent(FieldClassificationService.SERVICE_INTERFACE).setComponent(serviceName),
|
||||
/* bindingFlags= */ 0, userId, IFieldClassificationService.Stub::asInterface);
|
||||
mComponentName = serviceName;
|
||||
if (sDebug) {
|
||||
Slog.d(TAG, "About to connect to serviceName: " + serviceName);
|
||||
}
|
||||
// Bind right away.
|
||||
connect();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Pair<ServiceInfo, ComponentName> getComponentName(@NonNull String serviceName,
|
||||
@UserIdInt int userId, boolean isTemporary) {
|
||||
int flags = PackageManager.GET_META_DATA;
|
||||
if (!isTemporary) {
|
||||
flags |= PackageManager.MATCH_SYSTEM_ONLY;
|
||||
}
|
||||
|
||||
final ComponentName serviceComponent;
|
||||
ServiceInfo serviceInfo = null;
|
||||
try {
|
||||
serviceComponent = ComponentName.unflattenFromString(serviceName);
|
||||
serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, flags,
|
||||
userId);
|
||||
if (serviceInfo == null) {
|
||||
Slog.e(TAG, "Bad service name for flags " + flags + ": " + serviceName);
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Error getting service info for '" + serviceName + "': " + e);
|
||||
return null;
|
||||
}
|
||||
return new Pair<>(serviceInfo, serviceComponent);
|
||||
}
|
||||
|
||||
public ComponentName getComponentName() {
|
||||
return mComponentName;
|
||||
}
|
||||
|
||||
@Override // from ServiceConnector.Impl
|
||||
protected void onServiceConnectionStatusChanged(IFieldClassificationService service,
|
||||
boolean connected) {
|
||||
try {
|
||||
if (connected) {
|
||||
service.onConnected(false, false);
|
||||
} else {
|
||||
service.onDisconnected();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Slog.w(TAG,
|
||||
"Exception calling onServiceConnectionStatusChanged(" + connected + "): ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from AbstractRemoteService
|
||||
protected long getAutoDisconnectTimeoutMs() {
|
||||
return TIMEOUT_IDLE_UNBIND_MS;
|
||||
}
|
||||
|
||||
public void onFieldClassificationRequest(@NonNull FieldClassificationRequest request,
|
||||
FieldClassificationServiceCallbacks fieldClassificationServiceCallbacks) {
|
||||
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG, "onFieldClassificationRequest request:" + request);
|
||||
}
|
||||
|
||||
run(
|
||||
(s) ->
|
||||
s.onFieldClassificationRequest(
|
||||
request,
|
||||
new IFieldClassificationCallback.Stub() {
|
||||
@Override
|
||||
public void onCancellable(ICancellationSignal cancellation) {
|
||||
if (sDebug) {
|
||||
Log.d(TAG, "onCancellable");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(FieldClassificationResponse response) {
|
||||
if (sDebug) {
|
||||
Log.d(TAG, "onSuccess Response: " + response);
|
||||
}
|
||||
fieldClassificationServiceCallbacks
|
||||
.onClassificationRequestSuccess(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
if (sDebug) {
|
||||
Log.d(TAG, "onFailure");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompleted() throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() throws RemoteException {}
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -135,6 +135,9 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
|
||||
}
|
||||
|
||||
public void onFillRequest(@NonNull FillRequest request) {
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG, "onFillRequest:" + request);
|
||||
}
|
||||
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
|
||||
AtomicReference<CompletableFuture<FillResponse>> futureRef = new AtomicReference<>();
|
||||
|
||||
|
||||
@@ -88,6 +88,8 @@ import android.os.Process;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.service.assist.classification.FieldClassificationRequest;
|
||||
import android.service.assist.classification.FieldClassificationResponse;
|
||||
import android.service.autofill.AutofillFieldClassificationService.Scores;
|
||||
import android.service.autofill.AutofillService;
|
||||
import android.service.autofill.CompositeUserData;
|
||||
@@ -125,6 +127,7 @@ import android.view.autofill.AutofillValue;
|
||||
import android.view.autofill.IAutoFillManagerClient;
|
||||
import android.view.autofill.IAutofillWindowPresenter;
|
||||
import android.view.inputmethod.InlineSuggestionsRequest;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
@@ -146,6 +149,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -163,7 +167,8 @@ import java.util.function.Function;
|
||||
* until the user authenticates or it times out.
|
||||
*/
|
||||
final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
|
||||
AutoFillUI.AutoFillUiCallback, ValueFinder {
|
||||
AutoFillUI.AutoFillUiCallback, ValueFinder,
|
||||
RemoteFieldClassificationService.FieldClassificationServiceCallbacks {
|
||||
private static final String TAG = "AutofillSession";
|
||||
|
||||
private static final String ACTION_DELAYED_FILL =
|
||||
@@ -185,6 +190,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
|
||||
private static AtomicInteger sIdCounter = new AtomicInteger(2);
|
||||
|
||||
private static AtomicInteger sIdCounterForPcc = new AtomicInteger(2);
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private @SessionState int mSessionState = STATE_UNKNOWN;
|
||||
|
||||
@@ -395,6 +402,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
@Nullable
|
||||
private ClientSuggestionsSession mClientSuggestionsSession;
|
||||
|
||||
private final ClassificationState mClassificationState = new ClassificationState();
|
||||
|
||||
// TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
|
||||
// new one per Session.
|
||||
private final BroadcastReceiver mDelayedFillBroadcastReceiver =
|
||||
@@ -729,24 +738,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
* Assist Data Receiver for PCC
|
||||
*/
|
||||
private final class PccAssistDataReceiverImpl extends IAssistDataReceiver.Stub {
|
||||
// TODO: Uncomment lines below after field classification service definition merged
|
||||
// @GuardedBy("mLock")
|
||||
// private FieldClassificationRequest mPendingFieldClassifitacionRequest;
|
||||
// @GuardedBy("mLock")
|
||||
// private FieldClassificationRequest mLastFieldClassifitacionRequest;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
void maybeRequestFieldClassificationFromServiceLocked() {
|
||||
// TODO: Uncomment lines below after field classification service definition merged
|
||||
// if (mPendingFieldClassifitacionRequest == null) {
|
||||
// return;
|
||||
// }
|
||||
// mLastFieldClassifitacionRequest = mPendingFieldClassifitacionRequest;
|
||||
//
|
||||
// mRemoteFieldClassificationService.onFieldClassificationRequest(
|
||||
// mPendingFieldClassifitacionRequest);
|
||||
//
|
||||
// mPendingFieldClassifitacionRequest = null;
|
||||
if (mClassificationState.mPendingFieldClassificationRequest == null) {
|
||||
Log.w(TAG, "Received AssistData without pending classification request");
|
||||
return;
|
||||
}
|
||||
|
||||
RemoteFieldClassificationService remoteFieldClassificationService =
|
||||
mService.getRemoteFieldClassificationServiceLocked();
|
||||
if (remoteFieldClassificationService != null) {
|
||||
remoteFieldClassificationService.onFieldClassificationRequest(
|
||||
mClassificationState.mPendingFieldClassificationRequest, Session.this);
|
||||
}
|
||||
mClassificationState.onFieldClassificationRequestSent();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -793,12 +799,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
ids.get(i).setSessionId(Session.this.id);
|
||||
}
|
||||
|
||||
// TODO: Uncomment lines below after field classification service definition merged
|
||||
// FieldClassificationRequest request = new FieldClassificationRequest(structure);
|
||||
//
|
||||
// mPendingFieldClassifitacionRequest = request;
|
||||
//
|
||||
// maybeRequestFieldClassificationFromServiceLocked();
|
||||
mClassificationState.onAssistStructureReceived(structure);
|
||||
|
||||
maybeRequestFieldClassificationFromServiceLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1177,9 +1180,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
int requestId;
|
||||
// TODO(b/158623971): Update this to prevent possible overflow
|
||||
do {
|
||||
requestId = sIdCounter.getAndIncrement();
|
||||
requestId = sIdCounterForPcc.getAndIncrement();
|
||||
} while (requestId == INVALID_REQUEST_ID);
|
||||
|
||||
if (sVerbose) {
|
||||
Slog.v(TAG, "request id is " + requestId + ", requesting assist structure for pcc");
|
||||
}
|
||||
// Call requestAutofilLData
|
||||
try {
|
||||
final Bundle receiverExtras = new Bundle();
|
||||
@@ -1187,8 +1193,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
if (!ActivityTaskManager.getService().requestAutofillData(mPccAssistReceiver,
|
||||
receiverExtras, mActivityToken, flags)) {
|
||||
Slog.w(TAG, "failed to request autofill data for pcc: " + mActivityToken);
|
||||
receiverExtras, mActivityToken, flags)) {
|
||||
Slog.w(TAG, "failed to request autofill data for " + mActivityToken);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
@@ -1386,6 +1392,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Check if this is required. We can still present datasets to the user even if
|
||||
// traditional field classification is disabled.
|
||||
fieldClassificationIds = response.getFieldClassificationIds();
|
||||
if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null
|
||||
&& !mService.isFieldClassificationEnabledLocked()) {
|
||||
@@ -1470,11 +1478,231 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/266379948): Ideally wait for PCC request to finish for a while more
|
||||
// (say 100ms) before proceeding further on.
|
||||
|
||||
synchronized (mLock) {
|
||||
response = getEffectiveFillResponse(response);
|
||||
processResponseLocked(response, null, requestFlags);
|
||||
}
|
||||
}
|
||||
|
||||
private FillResponse getEffectiveFillResponse(FillResponse response) {
|
||||
// TODO(b/266379948): label dataset source
|
||||
if (!mService.getMaster().isPccClassificationEnabled()) return response;
|
||||
synchronized (mLock) {
|
||||
if (mClassificationState.mState != ClassificationState.STATE_RESPONSE
|
||||
|| mClassificationState.mLastFieldClassificationResponse == null) {
|
||||
return response;
|
||||
}
|
||||
if (!mClassificationState.processResponse()) return response;
|
||||
}
|
||||
boolean preferAutofillProvider = mService.getMaster().preferProviderOverPcc();
|
||||
boolean shouldUseFallback = mService.getMaster().shouldUsePccFallback();
|
||||
if (preferAutofillProvider && !shouldUseFallback) {
|
||||
return response;
|
||||
}
|
||||
|
||||
DatasetComputationContainer autofillProviderContainer = new DatasetComputationContainer();
|
||||
DatasetComputationContainer detectionPccContainer = new DatasetComputationContainer();
|
||||
|
||||
computeDatasetsForProviderAndUpdateContainer(response, autofillProviderContainer);
|
||||
computeDatasetsForPccAndUpdateContainer(response, detectionPccContainer);
|
||||
|
||||
DatasetComputationContainer resultContainer;
|
||||
if (preferAutofillProvider) {
|
||||
resultContainer = autofillProviderContainer;
|
||||
if (shouldUseFallback) {
|
||||
// add PCC datasets that are not detected by provider.
|
||||
addFallbackDatasets(autofillProviderContainer, detectionPccContainer);
|
||||
}
|
||||
} else {
|
||||
resultContainer = detectionPccContainer;
|
||||
if (shouldUseFallback) {
|
||||
// add Provider's datasets that are not detected by PCC.
|
||||
addFallbackDatasets(detectionPccContainer, autofillProviderContainer);
|
||||
}
|
||||
}
|
||||
// Create FillResponse with effectiveDatasets, and all the rest value from the original
|
||||
// response.
|
||||
return FillResponse.shallowCopy(response, new ArrayList<>(resultContainer.mDatasets));
|
||||
}
|
||||
|
||||
/**
|
||||
* A private class to hold & compute datasets to be shown
|
||||
*/
|
||||
private static class DatasetComputationContainer {
|
||||
// List of all autofill ids that have a corresponding datasets
|
||||
Set<AutofillId> mAutofillIds = new ArraySet<>();
|
||||
// Set of datasets. Kept separately, to be able to be used directly for composing
|
||||
// FillResponse.
|
||||
Set<Dataset> mDatasets = new ArraySet<>();
|
||||
ArrayMap<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new ArrayMap<>();
|
||||
}
|
||||
|
||||
// Adds fallback datasets to the first container.
|
||||
// This function will destruct and modify c2 container.
|
||||
private void addFallbackDatasets(
|
||||
DatasetComputationContainer c1, DatasetComputationContainer c2) {
|
||||
for (AutofillId id : c2.mAutofillIds) {
|
||||
if (!c1.mAutofillIds.contains(id)) {
|
||||
|
||||
// Since c2 could be modified in a previous iteration, it's possible that all
|
||||
// datasets corresponding to it have been evaluated, and it's map no longer has
|
||||
// any more datasets left. Early return in this case.
|
||||
if (c2.mAutofillIdToDatasetMap.get(id).isEmpty()) return;
|
||||
|
||||
// For AutofillId id, do the following
|
||||
// 1. Add all the datasets corresponding to it to c1's dataset, and update c1
|
||||
// properly.
|
||||
// 2. All the datasets that were added should be removed from the other autofill
|
||||
// ids that were in this dataset. This prevents us from revisiting those datasets.
|
||||
// Although we are using Sets, and that'd avoid re-adding them, using this logic
|
||||
// for now to keep safe. TODO(b/266379948): Revisit this logic.
|
||||
|
||||
Set<Dataset> datasets = c2.mAutofillIdToDatasetMap.get(id);
|
||||
Set<Dataset> copyDatasets = new ArraySet<>(datasets);
|
||||
c1.mAutofillIds.add(id);
|
||||
c1.mAutofillIdToDatasetMap.put(id, copyDatasets);
|
||||
c1.mDatasets.addAll(copyDatasets);
|
||||
|
||||
for (Dataset dataset : datasets) {
|
||||
for (AutofillId currentId : dataset.getFieldIds()) {
|
||||
if (currentId.equals(id)) continue;
|
||||
// For this id, we need to remove the dataset from it's map.
|
||||
c2.mAutofillIdToDatasetMap.get(currentId).remove(dataset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void computeDatasetsForProviderAndUpdateContainer(
|
||||
FillResponse response, DatasetComputationContainer container) {
|
||||
List<Dataset> datasets = response.getDatasets();
|
||||
if (datasets == null) return;
|
||||
ArrayMap<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new ArrayMap<>();
|
||||
Set<Dataset> eligibleDatasets = new ArraySet<>();
|
||||
Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
|
||||
for (Dataset dataset : response.getDatasets()) {
|
||||
if (dataset.getFieldIds() == null) continue;
|
||||
if (dataset.getAutofillDatatypes() != null
|
||||
&& dataset.getAutofillDatatypes().size() > 0) {
|
||||
continue;
|
||||
}
|
||||
eligibleDatasets.add(dataset);
|
||||
for (AutofillId id : dataset.getFieldIds()) {
|
||||
eligibleAutofillIds.add(id);
|
||||
Set<Dataset> datasetForIds = autofillIdToDatasetMap.get(id);
|
||||
if (datasetForIds == null) {
|
||||
datasetForIds = new ArraySet<>();
|
||||
}
|
||||
datasetForIds.add(dataset);
|
||||
autofillIdToDatasetMap.put(id, datasetForIds);
|
||||
}
|
||||
}
|
||||
container.mAutofillIdToDatasetMap = autofillIdToDatasetMap;
|
||||
container.mDatasets = eligibleDatasets;
|
||||
container.mAutofillIds = eligibleAutofillIds;
|
||||
}
|
||||
|
||||
private void computeDatasetsForPccAndUpdateContainer(
|
||||
FillResponse response, DatasetComputationContainer container) {
|
||||
List<Dataset> datasets = response.getDatasets();
|
||||
if (datasets == null) return;
|
||||
|
||||
synchronized (mLock) {
|
||||
ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap =
|
||||
mClassificationState.mHintsToAutofillIdMap;
|
||||
|
||||
ArrayMap<String, Set<AutofillId>> groupHintsToAutofillIdMap =
|
||||
mClassificationState.mGroupHintsToAutofillIdMap;
|
||||
|
||||
ArrayMap<AutofillId, Set<Dataset>> map = new ArrayMap<>();
|
||||
|
||||
Set<Dataset> eligibleDatasets = new ArraySet<>();
|
||||
Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
|
||||
|
||||
for (int i = 0; i < datasets.size(); i++) {
|
||||
Dataset dataset = datasets.get(i);
|
||||
if (dataset.getAutofillDatatypes() == null) continue;
|
||||
if (dataset.getFieldIds() != null && dataset.getFieldIds().size() > 0) continue;
|
||||
|
||||
ArrayList<AutofillId> fieldIds = new ArrayList<>();
|
||||
ArrayList<AutofillValue> fieldValues = new ArrayList<>();
|
||||
ArrayList<RemoteViews> fieldPresentations = new ArrayList<>();
|
||||
ArrayList<RemoteViews> fieldDialogPresentations = new ArrayList<>();
|
||||
ArrayList<InlinePresentation> fieldInlinePresentations = new ArrayList<>();
|
||||
ArrayList<InlinePresentation> fieldInlineTooltipPresentations = new ArrayList<>();
|
||||
ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
|
||||
|
||||
for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
|
||||
String hint = dataset.getAutofillDatatypes().get(j);
|
||||
|
||||
if (hintsToAutofillIdMap.containsKey(hint)) {
|
||||
ArrayList<AutofillId> tempIds =
|
||||
new ArrayList<>(hintsToAutofillIdMap.get(hint));
|
||||
|
||||
for (AutofillId autofillId : tempIds) {
|
||||
eligibleAutofillIds.add(autofillId);
|
||||
// For each of the field, copy over values.
|
||||
fieldIds.add(autofillId);
|
||||
fieldValues.add(dataset.getFieldValues().get(j));
|
||||
// TODO(b/266379948): might need to make it more efficient by not
|
||||
// copying over value if it didn't exist. This would require creating
|
||||
// a getter for the presentations arraylist.
|
||||
fieldPresentations.add(dataset.getFieldPresentation(j));
|
||||
fieldDialogPresentations.add(dataset.getFieldDialogPresentation(j));
|
||||
fieldInlinePresentations.add(dataset.getFieldInlinePresentation(j));
|
||||
fieldInlineTooltipPresentations.add(
|
||||
dataset.getFieldInlineTooltipPresentation(j));
|
||||
fieldFilters.add(dataset.getFilter(j));
|
||||
}
|
||||
|
||||
Dataset newDataset =
|
||||
new Dataset(
|
||||
fieldIds,
|
||||
fieldValues,
|
||||
fieldPresentations,
|
||||
fieldDialogPresentations,
|
||||
fieldInlinePresentations,
|
||||
fieldInlineTooltipPresentations,
|
||||
fieldFilters,
|
||||
new ArrayList<>(),
|
||||
dataset.getFieldContent(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
dataset.getId(),
|
||||
dataset.getAuthentication());
|
||||
eligibleDatasets.add(newDataset);
|
||||
|
||||
// Associate this dataset with all the ids that are represented with it.
|
||||
Set<Dataset> newDatasets;
|
||||
for (AutofillId autofillId : tempIds) {
|
||||
if (map.containsKey(autofillId)) {
|
||||
newDatasets = map.get(autofillId);
|
||||
} else {
|
||||
newDatasets = new ArraySet<>();
|
||||
}
|
||||
newDatasets.add(newDataset);
|
||||
map.put(autofillId, newDatasets);
|
||||
}
|
||||
}
|
||||
// TODO(b/266379948): handle the case:
|
||||
// groupHintsToAutofillIdMap.containsKey(hint))
|
||||
// but the autofill id not being applicable to other hints.
|
||||
// TODO(b/266379948): also handle the case where there could be more types in
|
||||
// the dataset, provided by the provider, however, they aren't applicable.
|
||||
}
|
||||
}
|
||||
container.mAutofillIds = eligibleAutofillIds;
|
||||
container.mDatasets = eligibleDatasets;
|
||||
container.mAutofillIdToDatasetMap = map;
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void processNullResponseOrFallbackLocked(int requestId, int flags) {
|
||||
if (!mSessionFlags.mClientSuggestionsEnabled) {
|
||||
@@ -4579,6 +4807,189 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class maintaining the state of the requests to
|
||||
* {@link android.service.assist.classification.FieldClassificationService}.
|
||||
*/
|
||||
private static final class ClassificationState {
|
||||
|
||||
/**
|
||||
* Initial state indicating that the request for classification hasn't been triggered yet.
|
||||
*/
|
||||
private static final int STATE_INITIAL = 1;
|
||||
/**
|
||||
* Assist request has been triggered, but awaiting response.
|
||||
*/
|
||||
private static final int STATE_PENDING_ASSIST_REQUEST = 2;
|
||||
/**
|
||||
* Classification request has been triggered, but awaiting response.
|
||||
*/
|
||||
private static final int STATE_PENDING_REQUEST = 3;
|
||||
/**
|
||||
* Classification response has been received.
|
||||
*/
|
||||
private static final int STATE_RESPONSE = 4;
|
||||
/**
|
||||
* Classification state has been invalidated, and the last response may no longer be valid.
|
||||
* This could occur due to various reasons like views changing their layouts, becoming
|
||||
* visible or invisible, thereby rendering previous response potentially inaccurate or
|
||||
* incomplete.
|
||||
*/
|
||||
private static final int STATE_INVALIDATED = 5;
|
||||
|
||||
@IntDef(prefix = { "STATE_" }, value = {
|
||||
STATE_INITIAL,
|
||||
STATE_PENDING_ASSIST_REQUEST,
|
||||
STATE_PENDING_REQUEST,
|
||||
STATE_RESPONSE,
|
||||
STATE_INVALIDATED
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface ClassificationRequestState{}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private @ClassificationRequestState int mState = STATE_INITIAL;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private FieldClassificationRequest mPendingFieldClassificationRequest;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private FieldClassificationResponse mLastFieldClassificationResponse;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private ArrayMap<AutofillId, Set<String>> mClassificationHintsMap;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private ArrayMap<AutofillId, Set<String>> mClassificationGroupHintsMap;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private ArrayMap<AutofillId, Set<String>> mClassificationCombinedHintsMap;
|
||||
|
||||
/**
|
||||
* Typically, there would be a 1:1 mapping. However, in certain cases, we may have a hint
|
||||
* being applicable to many types. An example of this being new/change password forms,
|
||||
* where you need to confirm the passward twice.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private ArrayMap<String, Set<AutofillId>> mHintsToAutofillIdMap;
|
||||
|
||||
/**
|
||||
* Group hints are expected to have a 1:many mapping. For example, different credit card
|
||||
* fields (creditCardNumber, expiry, cvv) will all map to the same group hints.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private ArrayMap<String, Set<AutofillId>> mGroupHintsToAutofillIdMap;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private String stateToString() {
|
||||
switch (mState) {
|
||||
case STATE_INITIAL:
|
||||
return "STATE_INITIAL";
|
||||
case STATE_PENDING_ASSIST_REQUEST:
|
||||
return "STATE_PENDING_ASSIST_REQUEST";
|
||||
case STATE_PENDING_REQUEST:
|
||||
return "STATE_PENDING_REQUEST";
|
||||
case STATE_RESPONSE:
|
||||
return "STATE_RESPONSE";
|
||||
case STATE_INVALIDATED:
|
||||
return "STATE_INVALIDATED";
|
||||
default:
|
||||
return "UNKNOWN_CLASSIFICATION_STATE_" + mState;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the response received.
|
||||
* @return true if the response was processed, false otherwise. If there wasn't any
|
||||
* response, yet this function was called, it would return false.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private boolean processResponse() {
|
||||
if (mClassificationHintsMap != null && !mClassificationHintsMap.isEmpty()) {
|
||||
// Already processed, so return
|
||||
return true;
|
||||
}
|
||||
|
||||
FieldClassificationResponse response = mLastFieldClassificationResponse;
|
||||
if (response == null) return false;
|
||||
|
||||
mClassificationHintsMap = new ArrayMap<>();
|
||||
mClassificationGroupHintsMap = new ArrayMap<>();
|
||||
mHintsToAutofillIdMap = new ArrayMap<>();
|
||||
mGroupHintsToAutofillIdMap = new ArrayMap<>();
|
||||
Set<android.service.assist.classification.FieldClassification> classifications =
|
||||
response.getClassifications();
|
||||
|
||||
for (android.service.assist.classification.FieldClassification classification :
|
||||
classifications) {
|
||||
AutofillId id = classification.getAutofillId();
|
||||
Set<String> hintDetections = classification.getHints();
|
||||
Set<String> groupHintsDetections = classification.getGroupHints();
|
||||
ArraySet<String> combinedHints = new ArraySet<>(hintDetections);
|
||||
mClassificationHintsMap.put(id, hintDetections);
|
||||
if (groupHintsDetections != null) {
|
||||
mClassificationGroupHintsMap.put(id, groupHintsDetections);
|
||||
combinedHints.addAll(groupHintsDetections);
|
||||
}
|
||||
mClassificationCombinedHintsMap.put(id, combinedHints);
|
||||
|
||||
processDetections(hintDetections, id, mHintsToAutofillIdMap);
|
||||
processDetections(groupHintsDetections, id, mGroupHintsToAutofillIdMap);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private static void processDetections(Set<String> detections, AutofillId id,
|
||||
ArrayMap<String, Set<AutofillId>> currentMap) {
|
||||
for (String detection : detections) {
|
||||
Set<AutofillId> autofillIds;
|
||||
if (currentMap.containsKey(detection)) {
|
||||
autofillIds = currentMap.get(detection);
|
||||
} else {
|
||||
autofillIds = new ArraySet<>();
|
||||
}
|
||||
autofillIds.add(id);
|
||||
currentMap.put(detection, autofillIds);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void invalidateState() {
|
||||
mState = STATE_INVALIDATED;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void updatePendingAssistData() {
|
||||
mState = STATE_PENDING_ASSIST_REQUEST;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void updatePendingRequest() {
|
||||
mState = STATE_PENDING_REQUEST;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void updateResponseReceived(FieldClassificationResponse response) {
|
||||
mState = STATE_RESPONSE;
|
||||
mLastFieldClassificationResponse = response;
|
||||
mPendingFieldClassificationRequest = null;
|
||||
processResponse();
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void onAssistStructureReceived(AssistStructure structure) {
|
||||
mState = STATE_PENDING_REQUEST;
|
||||
mPendingFieldClassificationRequest = new FieldClassificationRequest(structure);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void onFieldClassificationRequestSent() {
|
||||
mState = STATE_PENDING_REQUEST;
|
||||
mPendingFieldClassificationRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Session: [id=" + id + ", component=" + mComponentName
|
||||
@@ -5088,4 +5499,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|
||||
ServiceInfo serviceInfo = mService.getServiceInfo();
|
||||
return serviceInfo == null ? Process.INVALID_UID : serviceInfo.applicationInfo.uid;
|
||||
}
|
||||
|
||||
// DetectionServiceCallbacks
|
||||
public void onClassificationRequestSuccess(@Nullable FieldClassificationResponse response) {
|
||||
mClassificationState.updateResponseReceived(response);
|
||||
}
|
||||
|
||||
public void onClassificationRequestFailure(int requestId, @Nullable CharSequence message) {
|
||||
|
||||
}
|
||||
|
||||
public void onClassificationRequestTimeout(int requestId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDied(@NonNull RemoteFieldClassificationService service) {
|
||||
Slog.w(TAG, "removing session because service died");
|
||||
synchronized (mLock) {
|
||||
// TODO(b/266379948)
|
||||
// forceRemoveFromServiceLocked();
|
||||
}
|
||||
}
|
||||
// DetectionServiceCallbacks end
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user