diff --git a/services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java b/services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java new file mode 100644 index 0000000000000..b4aca1530204d --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 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 android.annotation.Nullable; +import android.os.Bundle; +import android.os.RemoteCallback; +import android.service.autofill.FieldClassification; +import android.service.autofill.FillEventHistory.Event.NoSaveReason; +import android.util.Slog; +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillManager.AutofillCommitReason; + +import java.util.ArrayList; + +class LogFieldClassificationScoreOnResultListener implements + RemoteCallback.OnResultListener { + + private static final String TAG = "LogFieldClassificationScoreOnResultListener"; + + private Session mSession; + private final @NoSaveReason int mSaveDialogNotShowReason; + private final @AutofillCommitReason int mCommitReason; + private final int mViewsSize; + private final AutofillId[] mAutofillIds; + private final String[] mUserValues; + private final String[] mCategoryIds; + private final ArrayList mDetectedFieldIds; + private final ArrayList mDetectedFieldClassifications; + LogFieldClassificationScoreOnResultListener(Session session, + int saveDialogNotShowReason, + int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues, + String[] categoryIds, ArrayList detectedFieldIds, + ArrayList detectedFieldClassifications) { + this.mSession = session; + this.mSaveDialogNotShowReason = saveDialogNotShowReason; + this.mCommitReason = commitReason; + this.mViewsSize = viewsSize; + this.mAutofillIds = autofillIds; + this.mUserValues = userValues; + this.mCategoryIds = categoryIds; + this.mDetectedFieldIds = detectedFieldIds; + this.mDetectedFieldClassifications = detectedFieldClassifications; + } + + public void onResult(@Nullable Bundle result) { + // Create a local copy to safe guard race condition + Session session = mSession; + if (session == null) { + Slog.wtf(TAG, "session is null when calling onResult()"); + return; + } + session.handleLogFieldClassificationScore( + result, + mSaveDialogNotShowReason, + mCommitReason, + mViewsSize, + mAutofillIds, + mUserValues, + mCategoryIds, + mDetectedFieldIds, + mDetectedFieldClassifications); + mSession = null; + } +} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 563e431b57372..0c3f8667f4f83 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -3063,78 +3063,93 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // Then use the results, asynchronously - final RemoteCallback callback = new RemoteCallback((result) -> { - if (result == null) { - if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results"); - logContextCommitted(null, null, saveDialogNotShowReason, commitReason); - return; - } - final Scores scores = result.getParcelable(EXTRA_SCORES, android.service.autofill.AutofillFieldClassificationService.Scores.class); - if (scores == null) { - Slog.w(TAG, "No field classification score on " + result); - return; - } - int i = 0, j = 0; - try { - // Iteract over all autofill fields first - for (i = 0; i < viewsSize; i++) { - final AutofillId autofillId = autofillIds[i]; - - // Search the best scores for each category (as some categories could have - // multiple user values - ArrayMap scoresByField = null; - for (j = 0; j < userValues.length; j++) { - final String categoryId = categoryIds[j]; - final float score = scores.scores[i][j]; - if (score > 0) { - if (scoresByField == null) { - scoresByField = new ArrayMap<>(userValues.length); - } - final Float currentScore = scoresByField.get(categoryId); - if (currentScore != null && currentScore > score) { - if (sVerbose) { - Slog.v(TAG, "skipping score " + score - + " because it's less than " + currentScore); - } - continue; - } - if (sVerbose) { - Slog.v(TAG, "adding score " + score + " at index " + j + " and id " - + autofillId); - } - scoresByField.put(categoryId, score); - } else if (sVerbose) { - Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId); - } - } - if (scoresByField == null) { - if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId); - continue; - } - - // Then create the matches for that autofill id - final ArrayList matches = new ArrayList<>(scoresByField.size()); - for (j = 0; j < scoresByField.size(); j++) { - final String fieldId = scoresByField.keyAt(j); - final float score = scoresByField.valueAt(j); - matches.add(new Match(fieldId, score)); - } - detectedFieldIds.add(autofillId); - detectedFieldClassifications.add(new FieldClassification(matches)); - } // for i - } catch (ArrayIndexOutOfBoundsException e) { - wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e); - return; - } - - logContextCommitted(detectedFieldIds, detectedFieldClassifications, - saveDialogNotShowReason, commitReason); - }); + final RemoteCallback callback = new RemoteCallback( + new LogFieldClassificationScoreOnResultListener( + this, + saveDialogNotShowReason, + commitReason, + viewsSize, + autofillIds, + userValues, + categoryIds, + detectedFieldIds, + detectedFieldClassifications)); fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds, defaultAlgorithm, defaultArgs, algorithms, args); } + void handleLogFieldClassificationScore(@Nullable Bundle result, int saveDialogNotShowReason, + int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues, + String[] categoryIds, ArrayList detectedFieldIds, + ArrayList detectedFieldClassifications) { + if (result == null) { + if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results"); + logContextCommitted(null, null, saveDialogNotShowReason, commitReason); + return; + } + final Scores scores = result.getParcelable(EXTRA_SCORES, + android.service.autofill.AutofillFieldClassificationService.Scores.class); + if (scores == null) { + Slog.w(TAG, "No field classification score on " + result); + return; + } + int i = 0, j = 0; + try { + // Iteract over all autofill fields first + for (i = 0; i < viewsSize; i++) { + final AutofillId autofillId = autofillIds[i]; + + // Search the best scores for each category (as some categories could have + // multiple user values + ArrayMap scoresByField = null; + for (j = 0; j < userValues.length; j++) { + final String categoryId = categoryIds[j]; + final float score = scores.scores[i][j]; + if (score > 0) { + if (scoresByField == null) { + scoresByField = new ArrayMap<>(userValues.length); + } + final Float currentScore = scoresByField.get(categoryId); + if (currentScore != null && currentScore > score) { + if (sVerbose) { + Slog.v(TAG, "skipping score " + score + + " because it's less than " + currentScore); + } + continue; + } + if (sVerbose) { + Slog.v(TAG, "adding score " + score + " at index " + j + " and id " + + autofillId); + } + scoresByField.put(categoryId, score); + } else if (sVerbose) { + Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId); + } + } + if (scoresByField == null) { + if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId); + continue; + } + + // Then create the matches for that autofill id + final ArrayList matches = new ArrayList<>(scoresByField.size()); + for (j = 0; j < scoresByField.size(); j++) { + final String fieldId = scoresByField.keyAt(j); + final float score = scoresByField.valueAt(j); + matches.add(new Match(fieldId, score)); + } + detectedFieldIds.add(autofillId); + detectedFieldClassifications.add(new FieldClassification(matches)); + } // for i + } catch (ArrayIndexOutOfBoundsException e) { + wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e); + return; + } + logContextCommitted(detectedFieldIds, detectedFieldClassifications, + saveDialogNotShowReason, commitReason); + } + /** * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} * when necessary.