Merge "Added more metrics for Autofill:" into oc-mr1-dev
am: d4f72ac3c4

Change-Id: I46c2bb4e865c06a84245800dddb0fe018d8a52af
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 61cbce9..27eeb2e 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -44,7 +44,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -1245,10 +1245,10 @@
                 }
             }
 
-            final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
-            log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
-            log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED,
-                    numApplied);
+            final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_DATASET_APPLIED)
+                    .setPackageName(mContext.getPackageName())
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount)
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
             mMetricsLogger.write(log);
         }
     }
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index a29af5d..f15749f 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4451,6 +4451,82 @@
     // OS: O MR
     FIELD_SELECTION_SMART_RANGE = 1123;
 
+    // The value of an autofillable and savable view was reset
+    // Package: Package of app that was autofilled
+    // OS: O MR
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // Tag FIELD_AUTOFILL_PREVIOUS_LENGTH: the previous length of the value
+    AUTOFILL_VALUE_RESET = 1124;
+
+    // Tag of AUTOFILL_VALUE_RESET
+    // OS: O MR
+    FIELD_AUTOFILL_PREVIOUS_LENGTH = 1125;
+
+    // An autofill dataset authentification succeeded
+    // Package: Package of app that was autofilled
+    // OS: O MR
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    AUTOFILL_DATASET_AUTHENTICATED = 1126;
+
+    // An autofill service provided an invalid dataset authentification
+    // Package: Package of app that was autofilled
+    // OS: O MR
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    AUTOFILL_INVALID_DATASET_AUTHENTICATION = 1127;
+
+    // An autofill service provided an invalid authentification extra
+    // Package: Package of app that was autofilled
+    // OS: O MR
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    AUTOFILL_INVALID_AUTHENTICATION = 1128;
+
+    // An autofill service used a custom description (using RemoteViews) in the Save affordance
+    // Package: Package of app that is autofilled
+    // OS: O MR
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
+    AUTOFILL_SAVE_CUSTOM_DESCRIPTION = 1129;
+
+    // FIELD - Type of save object passed by the service when the Save UI is shown
+    // OS: O MR
+    FIELD_AUTOFILL_SAVE_TYPE = 1130;
+
+    // An autofill service used a custom subtitle (String) in the Save affordance
+    // Package: Package of app that is autofilled
+    // OS: O MR
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
+    AUTOFILL_SAVE_CUSTOM_SUBTITLE = 1131;
+
+    // User tapped a link in the custom description of the Save affordance provided by an autofill service
+    // Package: Package of app that is autofilled
+    // OS: O MR
+    // Type TYPE_UNKNOWN: The link was not properly set by the service
+    // Type TYPE_OPEN: The link launched an activity
+    // Type TYPE_FAILURE: The link could not launc an activity
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
+    AUTOFILL_SAVE_LINK_TAPPED = 1132;
+
+    // Result of the validation on save when an autofill service provided a validator
+    // Package: Package of app that is autofilled
+    // OS: O MR
+    // Type TYPE_FAILURE: The validation could not be performed due to an error
+    // Type TYPE_SUCCESS: The validation passed
+    // Type TYPE_DISMISS: The validation failed
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
+    AUTOFILL_SAVE_VALIDATION = 1133;
+
+    // Result of an operation in the autofill save affordance after the user tapped a link in the custom description
+    // provided by the autofill service
+    // Package: Package of app that is autofilled
+    // OS: O MR
+    // Type TYPE_OPEN: The save affordance was restored
+    // Type TYPE_DISMISS: The save affordcance was destroyed
+    // Type TYPE_FAILURE: An invalid opperation was reported by the app's AutofillManager
+    AUTOFILL_PENDING_SAVE_UI_OPERATION = 1134;
+
     // ---- End O-MR1 Constants, all O-MR1 constants go above this line ----
 
     // Add new aosp constants above this line.
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index ddc819d..a1c75bf 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -650,7 +650,7 @@
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service == null) return false;
-                return Objects.equals(packageName, service.getPackageName());
+                return Objects.equals(packageName, service.getServicePackageName());
             }
         }
 
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 2b7a671..84628e7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -146,8 +146,9 @@
         updateLocked(disabled);
     }
 
+    @Nullable
     CharSequence getServiceName() {
-        final String packageName = getPackageName();
+        final String packageName = getServicePackageName();
         if (packageName == null) {
             return null;
         }
@@ -162,7 +163,8 @@
         }
     }
 
-    String getPackageName() {
+    @Nullable
+    String getServicePackageName() {
         final ComponentName serviceComponent = getServiceComponentName();
         if (serviceComponent != null) {
             return serviceComponent.getPackageName();
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 086dd29..236fbfd 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.metrics.LogMaker;
 import android.os.Bundle;
 import android.service.autofill.Dataset;
 import android.util.ArrayMap;
@@ -25,6 +26,8 @@
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Objects;
@@ -99,4 +102,14 @@
         }
         return fields;
     }
+
+    @NonNull
+    public static LogMaker newLogMaker(int category, String packageName,
+            String servicePackageName) {
+        final LogMaker log = new LogMaker(category).setPackageName(packageName);
+        if (servicePackageName != null) {
+            log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        }
+        return log;
+    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index dd0c874..db0f392 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -411,7 +411,7 @@
         mPackageName = packageName;
         mClient = IAutoFillManagerClient.Stub.asInterface(client);
 
-        mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName);
+        writeLog(MetricsEvent.AUTOFILL_SESSION_STARTED);
     }
 
     /**
@@ -471,13 +471,10 @@
             processResponseLocked(response, requestFlags);
         }
 
-        final LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
+        final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST, servicePackageName)
                 .setType(MetricsEvent.TYPE_SUCCESS)
-                .setPackageName(mPackageName)
                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
-                        response.getDatasets() == null ? 0 : response.getDatasets().size())
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE,
-                        servicePackageName);
+                        response.getDatasets() == null ? 0 : response.getDatasets().size());
         mMetricsLogger.write(log);
     }
 
@@ -493,10 +490,8 @@
             }
             mService.resetLastResponse();
         }
-        LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
-                .setType(MetricsEvent.TYPE_FAILURE)
-                .setPackageName(mPackageName)
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST, servicePackageName)
+                .setType(MetricsEvent.TYPE_FAILURE);
         mMetricsLogger.write(log);
 
         getUiForShowing().showError(message, this);
@@ -515,11 +510,8 @@
                 return;
             }
         }
-        LogMaker log = (new LogMaker(
-                MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
-                .setType(MetricsEvent.TYPE_SUCCESS)
-                .setPackageName(mPackageName)
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
+                .setType(MetricsEvent.TYPE_SUCCESS);
         mMetricsLogger.write(log);
 
         // Nothing left to do...
@@ -539,11 +531,8 @@
                 return;
             }
         }
-        LogMaker log = (new LogMaker(
-                MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
-                .setType(MetricsEvent.TYPE_FAILURE)
-                .setPackageName(mPackageName)
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
+                .setType(MetricsEvent.TYPE_FAILURE);
         mMetricsLogger.write(log);
 
         getUiForShowing().showError(message, this);
@@ -739,21 +728,22 @@
         final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
         if (sDebug) Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result);
         if (result instanceof FillResponse) {
-            final FillResponse response = (FillResponse) result;
-            mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName);
-            replaceResponseLocked(authenticatedResponse, response);
+            writeLog(MetricsEvent.AUTOFILL_AUTHENTICATED);
+            replaceResponseLocked(authenticatedResponse, (FillResponse) result);
         } else if (result instanceof Dataset) {
-            // TODO: add proper metric
             if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
+                writeLog(MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
                 final Dataset dataset = (Dataset) result;
                 authenticatedResponse.getDatasets().set(datasetIdx, dataset);
                 autoFill(requestId, datasetIdx, dataset, false);
+            } else {
+                writeLog(MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION);
             }
         } else {
             if (result != null) {
                 Slog.w(TAG, "service returned invalid auth type: " + result);
             }
-            // TODO: add proper metric (on else)
+            writeLog(MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION);
             processNullResponseLocked(0);
         }
     }
@@ -767,6 +757,44 @@
         mHasCallback = hasIt;
     }
 
+    @Nullable
+    private FillResponse getLastResponseLocked(@Nullable String logPrefix) {
+        if (mContexts == null) {
+            if (sDebug && logPrefix != null) Slog.d(TAG, logPrefix + ": no contexts");
+            return null;
+        }
+        if (mResponses == null) {
+            // Happens when the activity / session was finished before the service replied, or
+            // when the service cannot autofill it (and returned a null response).
+            if (sVerbose && logPrefix != null) {
+                Slog.v(TAG, logPrefix + ": no responses on session");
+            }
+            return null;
+        }
+
+        final int lastResponseIdx = getLastResponseIndexLocked();
+        if (lastResponseIdx < 0) {
+            if (logPrefix != null) {
+                Slog.w(TAG, logPrefix + ": did not get last response. mResponses=" + mResponses
+                        + ", mViewStates=" + mViewStates);
+            }
+            return null;
+        }
+
+        final FillResponse response = mResponses.valueAt(lastResponseIdx);
+        if (sVerbose && logPrefix != null) {
+            Slog.v(TAG, logPrefix + ": mResponses=" + mResponses + ", mContexts=" + mContexts
+                    + ", mViewStates=" + mViewStates);
+        }
+        return response;
+    }
+
+    @Nullable
+    private SaveInfo getSaveInfoLocked() {
+        final FillResponse response = getLastResponseLocked(null);
+        return response == null ? null : response.getSaveInfo();
+    }
+
     /**
      * Shows the save UI, when session can be saved.
      *
@@ -778,32 +806,8 @@
                     + id + " destroyed");
             return false;
         }
-        if (mContexts == null) {
-            Slog.d(TAG, "showSaveLocked(): no contexts");
-            return true;
-        }
-        if (mResponses == null) {
-            // Happens when the activity / session was finished before the service replied, or
-            // when the service cannot autofill it (and returned a null response).
-            if (sVerbose) {
-                Slog.v(TAG, "showSaveLocked(): no responses on session");
-            }
-            return true;
-        }
-
-        final int lastResponseIdx = getLastResponseIndexLocked();
-        if (lastResponseIdx < 0) {
-            Slog.w(TAG, "showSaveLocked(): did not get last response. mResponses=" + mResponses
-                    + ", mViewStates=" + mViewStates);
-            return true;
-        }
-
-        final FillResponse response = mResponses.valueAt(lastResponseIdx);
-        final SaveInfo saveInfo = response.getSaveInfo();
-        if (sVerbose) {
-            Slog.v(TAG, "showSaveLocked(): mResponses=" + mResponses + ", mContexts=" + mContexts
-                    + ", mViewStates=" + mViewStates);
-        }
+        final FillResponse response = getLastResponseLocked("showSaveLocked()");
+        final SaveInfo saveInfo = response == null ? null : response.getSaveInfo();
 
         /*
          * The Save dialog is only shown if all conditions below are met:
@@ -915,15 +919,21 @@
 
                 final InternalValidator validator = saveInfo.getValidator();
                 if (validator != null) {
+                    final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SAVE_VALIDATION);
                     boolean isValid;
                     try {
                         isValid = validator.isValid(valueFinder);
+                        log.setType(isValid
+                                ? MetricsEvent.TYPE_SUCCESS
+                                : MetricsEvent.TYPE_DISMISS);
                     } catch (Exception e) {
-                        Slog.e(TAG, "Not showing save UI because of exception during validation "
-                                + e.getClass());
+                        Slog.e(TAG, "Not showing save UI because validation failed:", e);
+                        log.setType(MetricsEvent.TYPE_FAILURE);
+                        mMetricsLogger.write(log);
                         return true;
                     }
 
+                    mMetricsLogger.write(log);
                     if (!isValid) {
                         Slog.i(TAG, "not showing save UI because fields failed validation");
                         return true;
@@ -971,7 +981,8 @@
                 final IAutoFillManagerClient client = getClient();
                 mPendingSaveUi = new PendingUi(mActivityToken, id, client);
                 getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
-                        saveInfo, valueFinder, mPackageName, this, mPendingSaveUi);
+                        mService.getServicePackageName(), saveInfo, valueFinder, mPackageName, this,
+                        mPendingSaveUi);
                 if (client != null) {
                     try {
                         client.setSaveUiState(id, true);
@@ -1236,6 +1247,21 @@
                 break;
             case ACTION_VALUE_CHANGED:
                 if (value != null && !value.equals(viewState.getCurrentValue())) {
+                    if (value.isEmpty()
+                            && viewState.getCurrentValue() != null
+                            && viewState.getCurrentValue().isText()
+                            && viewState.getCurrentValue().getTextValue() != null
+                            && getSaveInfoLocked() != null) {
+                        final int length = viewState.getCurrentValue().getTextValue().length();
+                        if (sDebug) {
+                            Slog.d(TAG, "updateLocked(" + id + "): resetting value that was "
+                                    + length + " chars long");
+                        }
+                        final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET)
+                                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length);
+                        mMetricsLogger.write(log);
+                    }
+
                     // Always update the internal state.
                     viewState.setCurrentValue(value);
 
@@ -1311,7 +1337,8 @@
             filterText = value.getTextValue().toString();
         }
 
-        getUiForShowing().showFillUi(filledId, response, filterText, mPackageName, this);
+        getUiForShowing().showFillUi(filledId, response, filterText,
+                mService.getServicePackageName(), mPackageName, this);
     }
 
     boolean isDestroyed() {
@@ -1720,7 +1747,7 @@
         mUi.destroyAll(mPendingSaveUi, this);
         mUi.clearCallback(this);
         mDestroyed = true;
-        mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName);
+        writeLog(MetricsEvent.AUTOFILL_SESSION_FINISHED);
         return mRemoteFillService;
     }
 
@@ -1805,4 +1832,16 @@
         }
         return lastResponseIdx;
     }
+
+    private LogMaker newLogMaker(int category) {
+        return newLogMaker(category, mService.getServicePackageName());
+    }
+
+    private LogMaker newLogMaker(int category, String servicePackageName) {
+        return Helper.newLogMaker(category, mPackageName, servicePackageName);
+    }
+
+    private void writeLog(int category) {
+        mMetricsLogger.write(newLogMaker(category));
+    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index cac2bff..434b590 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -40,8 +40,9 @@
 import android.widget.Toast;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.UiThread;
+import com.android.server.autofill.Helper;
 
 import java.io.PrintWriter;
 
@@ -158,21 +159,22 @@
      * @param focusedId the currently focused field
      * @param response the current fill response
      * @param filterText text of the view to be filled
+     * @param servicePackageName package name of the autofill service filling the activity
      * @param packageName package name of the activity that is filled
      * @param callback Identifier for the caller
      */
     public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
-            @Nullable String filterText, @NonNull String packageName,
-            @NonNull AutoFillUiCallback callback) {
+            @Nullable String filterText, @Nullable String servicePackageName,
+            @NonNull String packageName, @NonNull AutoFillUiCallback callback) {
         if (sDebug) {
             final int size = filterText == null ? 0 : filterText.length();
             Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars");
         }
-        final LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI))
-                .setPackageName(packageName)
-                .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
+        final LogMaker log =
+                Helper.newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, packageName, servicePackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
                         filterText == null ? 0 : filterText.length())
-                .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
                         response.getDatasets() == null ? 0 : response.getDatasets().size());
 
         mHandler.post(() -> {
@@ -184,7 +186,7 @@
                     filterText, mOverlayControl, new FillUi.Callback() {
                 @Override
                 public void onResponsePicked(FillResponse response) {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_DETAIL);
+                    log.setType(MetricsEvent.TYPE_DETAIL);
                     hideFillUiUiThread(callback);
                     if (mCallback != null) {
                         mCallback.authenticate(response.getRequestId(),
@@ -195,7 +197,7 @@
 
                 @Override
                 public void onDatasetPicked(Dataset dataset) {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+                    log.setType(MetricsEvent.TYPE_ACTION);
                     hideFillUiUiThread(callback);
                     if (mCallback != null) {
                         final int datasetIndex = response.getDatasets().indexOf(dataset);
@@ -205,14 +207,14 @@
 
                 @Override
                 public void onCanceled() {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS);
+                    log.setType(MetricsEvent.TYPE_DISMISS);
                     hideFillUiUiThread(callback);
                 }
 
                 @Override
                 public void onDestroy() {
-                    if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) {
-                        log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
+                    if (log.getType() == MetricsEvent.TYPE_UNKNOWN) {
+                        log.setType(MetricsEvent.TYPE_CLOSE);
                     }
                     mMetricsLogger.write(log);
                 }
@@ -246,27 +248,29 @@
      * Shows the UI asking the user to save for autofill.
      */
     public void showSaveUi(@NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
-            @NonNull SaveInfo info,@NonNull ValueFinder valueFinder, @NonNull String packageName,
+            @Nullable String servicePackageName, @NonNull SaveInfo info,
+            @NonNull ValueFinder valueFinder, @NonNull String packageName,
             @NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi) {
         if (sVerbose) Slog.v(TAG, "showSaveUi() for " + packageName + ": " + info);
         int numIds = 0;
         numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length;
         numIds += info.getOptionalIds() == null ? 0 : info.getOptionalIds().length;
 
-        LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_SAVE_UI))
-                .setPackageName(packageName).addTaggedData(
-                        MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
+        final LogMaker log =
+                Helper.newLogMaker(MetricsEvent.AUTOFILL_SAVE_UI, packageName, servicePackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
 
         mHandler.post(() -> {
             if (callback != mCallback) {
                 return;
             }
             hideAllUiThread(callback);
-            mSaveUi = new SaveUi(mContext, pendingSaveUi, serviceLabel, serviceIcon, info,
-                    valueFinder, mOverlayControl, new SaveUi.OnSaveListener() {
+            mSaveUi = new SaveUi(mContext, pendingSaveUi, serviceLabel, serviceIcon,
+                    servicePackageName, packageName, info, valueFinder, mOverlayControl,
+                    new SaveUi.OnSaveListener() {
                 @Override
                 public void onSave() {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+                    log.setType(MetricsEvent.TYPE_ACTION);
                     hideSaveUiUiThread(mCallback);
                     if (mCallback != null) {
                         mCallback.save();
@@ -276,7 +280,7 @@
 
                 @Override
                 public void onCancel(IntentSender listener) {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS);
+                    log.setType(MetricsEvent.TYPE_DISMISS);
                     hideSaveUiUiThread(mCallback);
                     if (listener != null) {
                         try {
@@ -294,8 +298,8 @@
 
                 @Override
                 public void onDestroy() {
-                    if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) {
-                        log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
+                    if (log.getType() == MetricsEvent.TYPE_UNKNOWN) {
+                        log.setType(MetricsEvent.TYPE_CLOSE);
 
                         if (mCallback != null) {
                             mCallback.cancelSave();
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index d0b2e92..256c3ae 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -20,6 +20,7 @@
 import static com.android.server.autofill.Helper.sVerbose;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Dialog;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -30,6 +31,7 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -53,6 +55,8 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.UiThread;
 
 import java.io.PrintWriter;
@@ -111,6 +115,7 @@
     }
 
     private final Handler mHandler = UiThread.getHandler();
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     private final @NonNull Dialog mDialog;
 
@@ -121,16 +126,21 @@
     private final CharSequence mTitle;
     private final CharSequence mSubTitle;
     private final PendingUi mPendingUi;
+    private final String mServicePackageName;
+    private final String mPackageName;
 
     private boolean mDestroyed;
 
     SaveUi(@NonNull Context context, @NonNull PendingUi pendingUi,
            @NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
+           @Nullable String servicePackageName, @NonNull String packageName,
            @NonNull SaveInfo info, @NonNull ValueFinder valueFinder,
            @NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener) {
         mPendingUi= pendingUi;
         mListener = new OneTimeListener(listener);
         mOverlayControl = overlayControl;
+        mServicePackageName = servicePackageName;
+        mPackageName = packageName;
 
         final LayoutInflater inflater = LayoutInflater.from(context);
         final View view = inflater.inflate(R.layout.autofill_save, null);
@@ -181,6 +191,8 @@
         ScrollView subtitleContainer = null;
         final CustomDescription customDescription = info.getCustomDescription();
         if (customDescription != null) {
+            writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION, type);
+
             mSubTitle = null;
             if (sDebug) Slog.d(TAG, "Using custom description");
 
@@ -190,40 +202,35 @@
                     @Override
                     public boolean onClickHandler(View view, PendingIntent pendingIntent,
                             Intent intent) {
+                        final LogMaker log =
+                                newLogMaker(MetricsEvent.AUTOFILL_SAVE_LINK_TAPPED, type);
                         // We need to hide the Save UI before launching the pending intent, and
                         // restore back it once the activity is finished, and that's achieved by
                         // adding a custom extra in the activity intent.
-                        if (pendingIntent != null) {
-                            if (intent == null) {
-                                Slog.w(TAG,
-                                        "remote view on custom description does not have intent");
-                                return false;
-                            }
-                            if (!pendingIntent.isActivity()) {
-                                Slog.w(TAG, "ignoring custom description pending intent that's not "
-                                        + "for an activity: " + pendingIntent);
-                                return false;
-                            }
-                            if (sVerbose) {
-                                Slog.v(TAG,
-                                        "Intercepting custom description intent: " + intent);
-                            }
-                            final IBinder token = mPendingUi.getToken();
-                            intent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
-                            try {
-                                pendingUi.client.startIntentSender(pendingIntent.getIntentSender(),
-                                        intent);
-                                mPendingUi.setState(PendingUi.STATE_PENDING);
-                                if (sDebug) {
-                                    Slog.d(TAG, "hiding UI until restored with token " + token);
-                                }
-                                hide();
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "error triggering pending intent: " + intent);
-                                return false;
-                            }
+                        final boolean isValid = isValidLink(pendingIntent, intent);
+                        if (!isValid) {
+                            log.setType(MetricsEvent.TYPE_UNKNOWN);
+                            mMetricsLogger.write(log);
+                            return false;
                         }
-                        return true;
+                        if (sVerbose) Slog.v(TAG, "Intercepting custom description intent");
+                        final IBinder token = mPendingUi.getToken();
+                        intent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
+                        try {
+                            pendingUi.client.startIntentSender(pendingIntent.getIntentSender(),
+                                    intent);
+                            mPendingUi.setState(PendingUi.STATE_PENDING);
+                            if (sDebug) Slog.d(TAG, "hiding UI until restored with token " + token);
+                            hide();
+                            log.setType(MetricsEvent.TYPE_OPEN);
+                            mMetricsLogger.write(log);
+                            return true;
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "error triggering pending intent: " + intent);
+                            log.setType(MetricsEvent.TYPE_FAILURE);
+                            mMetricsLogger.write(log);
+                            return false;
+                        }
                     }
                 };
 
@@ -241,6 +248,7 @@
         } else {
             mSubTitle = info.getDescription();
             if (mSubTitle != null) {
+                writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_SUBTITLE, type);
                 subtitleContainer = view.findViewById(R.id.autofill_save_custom_subtitle);
                 final TextView subtitleView = new TextView(context);
                 subtitleView.setText(mSubTitle);
@@ -313,6 +321,37 @@
         }
     }
 
+    private static boolean isValidLink(PendingIntent pendingIntent, Intent intent) {
+        if (pendingIntent == null) {
+            Slog.w(TAG, "isValidLink(): custom description without pending intent");
+            return false;
+        }
+        if (!pendingIntent.isActivity()) {
+            Slog.w(TAG, "isValidLink(): pending intent not for activity");
+            return false;
+        }
+        if (intent == null) {
+            Slog.w(TAG, "isValidLink(): no intent");
+            return false;
+        }
+        return true;
+    }
+
+    private LogMaker newLogMaker(int category, int saveType) {
+        return newLogMaker(category)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SAVE_TYPE, saveType);
+    }
+
+    private LogMaker newLogMaker(int category) {
+        return new LogMaker(category)
+                .setPackageName(mPackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, mServicePackageName);
+    }
+
+    private void writeLog(int category, int saveType) {
+        mMetricsLogger.write(newLogMaker(category, saveType));
+    }
+
     /**
      * Update the pending UI, if any.
      *
@@ -326,17 +365,25 @@
                     + mPendingUi.getToken());
             return;
         }
-        switch (operation) {
-            case AutofillManager.PENDING_UI_OPERATION_RESTORE:
-                if (sDebug) Slog.d(TAG, "Restoring save dialog for " + token);
-                show();
-                break;
-            case AutofillManager.PENDING_UI_OPERATION_CANCEL:
-                if (sDebug) Slog.d(TAG, "Cancelling pending save dialog for " + token);
-                hide();
-                break;
-            default:
-                Slog.w(TAG, "restore(): invalid operation " + operation);
+        final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_PENDING_SAVE_UI_OPERATION);
+        try {
+            switch (operation) {
+                case AutofillManager.PENDING_UI_OPERATION_RESTORE:
+                    if (sDebug) Slog.d(TAG, "Restoring save dialog for " + token);
+                    log.setType(MetricsEvent.TYPE_OPEN);
+                    show();
+                    break;
+                case AutofillManager.PENDING_UI_OPERATION_CANCEL:
+                    log.setType(MetricsEvent.TYPE_DISMISS);
+                    if (sDebug) Slog.d(TAG, "Cancelling pending save dialog for " + token);
+                    hide();
+                    break;
+                default:
+                    log.setType(MetricsEvent.TYPE_FAILURE);
+                    Slog.w(TAG, "restore(): invalid operation " + operation);
+            }
+        } finally {
+            mMetricsLogger.write(log);
         }
         mPendingUi.setState(PendingUi.STATE_FINISHED);
     }
@@ -385,6 +432,8 @@
         pw.print(prefix); pw.print("title: "); pw.println(mTitle);
         pw.print(prefix); pw.print("subtitle: "); pw.println(mSubTitle);
         pw.print(prefix); pw.print("pendingUi: "); pw.println(mPendingUi);
+        pw.print(prefix); pw.print("service: "); pw.println(mServicePackageName);
+        pw.print(prefix); pw.print("app: "); pw.println(mPackageName);
 
         final View view = mDialog.getWindow().getDecorView();
         final int[] loc = view.getLocationOnScreen();