[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:
Simranjit Kohli
2023-02-07 22:10:20 -08:00
parent 79321ffc40
commit 2de210ac38
8 changed files with 1017 additions and 56 deletions

View File

@@ -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

View File

@@ -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.
*

View File

@@ -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.
*

View File

@@ -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) {

View File

@@ -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

View File

@@ -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 {}
}));
}
}

View File

@@ -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<>();

View File

@@ -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
}