1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.autofill; 18 19 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG; 20 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE; 21 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU; 22 import static android.service.autofill.FillEventHistory.Event.UiType; 23 import static android.view.autofill.AutofillManager.COMMIT_REASON_ACTIVITY_FINISHED; 24 import static android.view.autofill.AutofillManager.COMMIT_REASON_VIEW_CHANGED; 25 import static android.view.autofill.AutofillManager.COMMIT_REASON_VIEW_CLICKED; 26 import static android.view.autofill.AutofillManager.COMMIT_REASON_VIEW_COMMITTED; 27 28 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED; 29 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__DIALOG; 30 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE; 31 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU; 32 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE; 33 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN; 34 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED; 35 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_FILL_REQUEST_FAILED; 36 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_NO_FOCUS; 37 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_REQUEST_TIMEOUT; 38 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY; 39 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON; 40 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED; 41 import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED; 42 import static com.android.server.autofill.Helper.sVerbose; 43 44 import android.annotation.IntDef; 45 import android.annotation.Nullable; 46 import android.content.ComponentName; 47 import android.content.Context; 48 import android.content.pm.PackageManager; 49 import android.provider.Settings; 50 import android.service.autofill.Dataset; 51 import android.text.TextUtils; 52 import android.util.Slog; 53 import android.view.autofill.AutofillId; 54 import android.view.autofill.AutofillManager; 55 56 import com.android.internal.util.FrameworkStatsLog; 57 58 import java.lang.annotation.Retention; 59 import java.lang.annotation.RetentionPolicy; 60 import java.util.List; 61 import java.util.Optional; 62 63 /** Helper class to track and log Autofill presentation stats. */ 64 public final class PresentationStatsEventLogger { 65 private static final String TAG = "PresentationStatsEventLogger"; 66 67 /** 68 * Reasons why presentation was not shown. These are wrappers around 69 * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.PresentationEventResult}. 70 */ 71 @IntDef(prefix = {"NOT_SHOWN_REASON"}, value = { 72 NOT_SHOWN_REASON_ANY_SHOWN, 73 NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED, 74 NOT_SHOWN_REASON_VIEW_CHANGED, 75 NOT_SHOWN_REASON_ACTIVITY_FINISHED, 76 NOT_SHOWN_REASON_REQUEST_TIMEOUT, 77 NOT_SHOWN_REASON_REQUEST_FAILED, 78 NOT_SHOWN_REASON_NO_FOCUS, 79 NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY, 80 NOT_SHOWN_REASON_UNKNOWN 81 }) 82 @Retention(RetentionPolicy.SOURCE) 83 public @interface NotShownReason {} 84 85 public static final int NOT_SHOWN_REASON_ANY_SHOWN = 86 AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN; 87 public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED = 88 AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED; 89 public static final int NOT_SHOWN_REASON_VIEW_CHANGED = 90 AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED; 91 public static final int NOT_SHOWN_REASON_ACTIVITY_FINISHED = 92 AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED; 93 public static final int NOT_SHOWN_REASON_REQUEST_TIMEOUT = 94 AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_REQUEST_TIMEOUT; 95 public static final int NOT_SHOWN_REASON_REQUEST_FAILED = 96 AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_FILL_REQUEST_FAILED; 97 public static final int NOT_SHOWN_REASON_NO_FOCUS = 98 AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_NO_FOCUS; 99 public static final int NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY = 100 AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY; 101 public static final int NOT_SHOWN_REASON_UNKNOWN = 102 AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON; 103 104 private final int mSessionId; 105 private Optional<PresentationStatsEventInternal> mEventInternal; 106 PresentationStatsEventLogger(int sessionId)107 private PresentationStatsEventLogger(int sessionId) { 108 mSessionId = sessionId; 109 mEventInternal = Optional.empty(); 110 } 111 forSessionId(int sessionId)112 public static PresentationStatsEventLogger forSessionId(int sessionId) { 113 return new PresentationStatsEventLogger(sessionId); 114 } 115 startNewEvent()116 public void startNewEvent() { 117 if (mEventInternal.isPresent()) { 118 Slog.e(TAG, "Failed to start new event because already have active event."); 119 return; 120 } 121 mEventInternal = Optional.of(new PresentationStatsEventInternal()); 122 } 123 maybeSetRequestId(int requestId)124 public void maybeSetRequestId(int requestId) { 125 mEventInternal.ifPresent(event -> event.mRequestId = requestId); 126 } 127 maybeSetNoPresentationEventReason(@otShownReason int reason)128 public void maybeSetNoPresentationEventReason(@NotShownReason int reason) { 129 mEventInternal.ifPresent(event -> { 130 if (event.mCountShown == 0) { 131 event.mNoPresentationReason = reason; 132 } 133 }); 134 } 135 maybeSetNoPresentationEventReasonIfNoReasonExists(@otShownReason int reason)136 public void maybeSetNoPresentationEventReasonIfNoReasonExists(@NotShownReason int reason) { 137 mEventInternal.ifPresent(event -> { 138 if (event.mCountShown == 0 && event.mNoPresentationReason == NOT_SHOWN_REASON_UNKNOWN) { 139 event.mNoPresentationReason = reason; 140 } 141 }); 142 } 143 maybeSetAvailableCount(@ullable List<Dataset> datasetList, AutofillId currentViewId)144 public void maybeSetAvailableCount(@Nullable List<Dataset> datasetList, 145 AutofillId currentViewId) { 146 mEventInternal.ifPresent(event -> { 147 int availableCount = getDatasetCountForAutofillId(datasetList, currentViewId); 148 event.mAvailableCount = availableCount; 149 event.mIsDatasetAvailable = availableCount > 0; 150 }); 151 } 152 maybeSetCountShown(@ullable List<Dataset> datasetList, AutofillId currentViewId)153 public void maybeSetCountShown(@Nullable List<Dataset> datasetList, 154 AutofillId currentViewId) { 155 mEventInternal.ifPresent(event -> { 156 int countShown = getDatasetCountForAutofillId(datasetList, currentViewId); 157 event.mCountShown = countShown; 158 if (countShown > 0) { 159 event.mNoPresentationReason = NOT_SHOWN_REASON_ANY_SHOWN; 160 } 161 }); 162 } 163 getDatasetCountForAutofillId(@ullable List<Dataset> datasetList, AutofillId currentViewId)164 private static int getDatasetCountForAutofillId(@Nullable List<Dataset> datasetList, 165 AutofillId currentViewId) { 166 int availableCount = 0; 167 if (datasetList != null) { 168 for (int i = 0; i < datasetList.size(); i++) { 169 Dataset data = datasetList.get(i); 170 if (data != null && data.getFieldIds() != null 171 && data.getFieldIds().contains(currentViewId)) { 172 availableCount += 1; 173 } 174 } 175 } 176 return availableCount; 177 } 178 maybeSetCountFilteredUserTyping(int countFilteredUserTyping)179 public void maybeSetCountFilteredUserTyping(int countFilteredUserTyping) { 180 mEventInternal.ifPresent(event -> { 181 event.mCountFilteredUserTyping = countFilteredUserTyping; 182 }); 183 } 184 maybeSetCountNotShownImePresentationNotDrawn( int countNotShownImePresentationNotDrawn)185 public void maybeSetCountNotShownImePresentationNotDrawn( 186 int countNotShownImePresentationNotDrawn) { 187 mEventInternal.ifPresent(event -> { 188 event.mCountNotShownImePresentationNotDrawn = countNotShownImePresentationNotDrawn; 189 }); 190 } 191 maybeSetCountNotShownImeUserNotSeen(int countNotShownImeUserNotSeen)192 public void maybeSetCountNotShownImeUserNotSeen(int countNotShownImeUserNotSeen) { 193 mEventInternal.ifPresent(event -> { 194 event.mCountNotShownImeUserNotSeen = countNotShownImeUserNotSeen; 195 }); 196 } 197 maybeSetDisplayPresentationType(@iType int uiType)198 public void maybeSetDisplayPresentationType(@UiType int uiType) { 199 mEventInternal.ifPresent(event -> { 200 event.mDisplayPresentationType = getDisplayPresentationType(uiType); 201 }); 202 } 203 maybeSetFillRequestSentTimestampMs(int timestamp)204 public void maybeSetFillRequestSentTimestampMs(int timestamp) { 205 mEventInternal.ifPresent(event -> { 206 event.mFillRequestSentTimestampMs = timestamp; 207 }); 208 } 209 maybeSetFillResponseReceivedTimestampMs(int timestamp)210 public void maybeSetFillResponseReceivedTimestampMs(int timestamp) { 211 mEventInternal.ifPresent(event -> { 212 event.mFillResponseReceivedTimestampMs = timestamp; 213 }); 214 } 215 maybeSetSuggestionSentTimestampMs(int timestamp)216 public void maybeSetSuggestionSentTimestampMs(int timestamp) { 217 mEventInternal.ifPresent(event -> { 218 event.mSuggestionSentTimestampMs = timestamp; 219 }); 220 } 221 maybeSetSuggestionPresentedTimestampMs(int timestamp)222 public void maybeSetSuggestionPresentedTimestampMs(int timestamp) { 223 mEventInternal.ifPresent(event -> { 224 event.mSuggestionPresentedTimestampMs = timestamp; 225 }); 226 } 227 maybeSetInlinePresentationAndSuggestionHostUid(Context context, int userId)228 public void maybeSetInlinePresentationAndSuggestionHostUid(Context context, int userId) { 229 mEventInternal.ifPresent(event -> { 230 event.mDisplayPresentationType = 231 AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE; 232 String imeString = Settings.Secure.getStringForUser(context.getContentResolver(), 233 Settings.Secure.DEFAULT_INPUT_METHOD, userId); 234 if (TextUtils.isEmpty(imeString)) { 235 Slog.w(TAG, "No default IME found"); 236 return; 237 } 238 ComponentName imeComponent = ComponentName.unflattenFromString(imeString); 239 if (imeComponent == null) { 240 Slog.w(TAG, "No default IME found"); 241 return; 242 } 243 int imeUid; 244 String packageName = imeComponent.getPackageName(); 245 try { 246 imeUid = context.getPackageManager().getApplicationInfoAsUser(packageName, 247 PackageManager.ApplicationInfoFlags.of(0), userId).uid; 248 } catch (PackageManager.NameNotFoundException e) { 249 Slog.w(TAG, "Couldn't find packageName: " + packageName); 250 return; 251 } 252 event.mInlineSuggestionHostUid = imeUid; 253 }); 254 } 255 maybeSetAutofillServiceUid(int uid)256 public void maybeSetAutofillServiceUid(int uid) { 257 mEventInternal.ifPresent(event -> { 258 event.mAutofillServiceUid = uid; 259 }); 260 } 261 maybeSetIsNewRequest(boolean isRequestTriggered)262 public void maybeSetIsNewRequest(boolean isRequestTriggered) { 263 mEventInternal.ifPresent(event -> { 264 event.mIsRequestTriggered = isRequestTriggered; 265 }); 266 } 267 logAndEndEvent()268 public void logAndEndEvent() { 269 if (!mEventInternal.isPresent()) { 270 Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same " 271 + "event"); 272 return; 273 } 274 PresentationStatsEventInternal event = mEventInternal.get(); 275 if (sVerbose) { 276 Slog.v(TAG, "Log AutofillPresentationEventReported:" 277 + " requestId=" + event.mRequestId 278 + " sessionId=" + mSessionId 279 + " mNoPresentationEventReason=" + event.mNoPresentationReason 280 + " mAvailableCount=" + event.mAvailableCount 281 + " mCountShown=" + event.mCountShown 282 + " mCountFilteredUserTyping=" + event.mCountFilteredUserTyping 283 + " mCountNotShownImePresentationNotDrawn=" 284 + event.mCountNotShownImePresentationNotDrawn 285 + " mCountNotShownImeUserNotSeen=" + event.mCountNotShownImeUserNotSeen 286 + " mDisplayPresentationType=" + event.mDisplayPresentationType 287 + " mAutofillServiceUid=" + event.mAutofillServiceUid 288 + " mInlineSuggestionHostUid=" + event.mInlineSuggestionHostUid 289 + " mIsRequestTriggered=" + event.mIsRequestTriggered 290 + " mFillRequestSentTimestampMs=" + event.mFillRequestSentTimestampMs 291 + " mFillResponseReceivedTimestampMs=" + event.mFillResponseReceivedTimestampMs 292 + " mSuggestionSentTimestampMs=" + event.mSuggestionSentTimestampMs 293 + " mSuggestionPresentedTimestampMs=" + event.mSuggestionPresentedTimestampMs); 294 } 295 296 // TODO(b/234185326): Distinguish empty responses from other no presentation reasons. 297 if (!event.mIsDatasetAvailable) { 298 mEventInternal = Optional.empty(); 299 return; 300 } 301 FrameworkStatsLog.write( 302 AUTOFILL_PRESENTATION_EVENT_REPORTED, 303 event.mRequestId, 304 mSessionId, 305 event.mNoPresentationReason, 306 event.mAvailableCount, 307 event.mCountShown, 308 event.mCountFilteredUserTyping, 309 event.mCountNotShownImePresentationNotDrawn, 310 event.mCountNotShownImeUserNotSeen, 311 event.mDisplayPresentationType, 312 event.mAutofillServiceUid, 313 event.mInlineSuggestionHostUid, 314 event.mIsRequestTriggered, 315 event.mFillRequestSentTimestampMs, 316 event.mFillResponseReceivedTimestampMs, 317 event.mSuggestionSentTimestampMs, 318 event.mSuggestionPresentedTimestampMs); 319 mEventInternal = Optional.empty(); 320 } 321 322 private final class PresentationStatsEventInternal { 323 int mRequestId; 324 @NotShownReason int mNoPresentationReason = NOT_SHOWN_REASON_UNKNOWN; 325 boolean mIsDatasetAvailable; 326 int mAvailableCount; 327 int mCountShown; 328 int mCountFilteredUserTyping; 329 int mCountNotShownImePresentationNotDrawn; 330 int mCountNotShownImeUserNotSeen; 331 int mDisplayPresentationType = AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE; 332 int mAutofillServiceUid = -1; 333 int mInlineSuggestionHostUid = -1; 334 boolean mIsRequestTriggered; 335 int mFillRequestSentTimestampMs; 336 int mFillResponseReceivedTimestampMs; 337 int mSuggestionSentTimestampMs; 338 int mSuggestionPresentedTimestampMs; 339 PresentationStatsEventInternal()340 PresentationStatsEventInternal() {} 341 } 342 getNoPresentationEventReason( @utofillManager.AutofillCommitReason int commitReason)343 static int getNoPresentationEventReason( 344 @AutofillManager.AutofillCommitReason int commitReason) { 345 switch (commitReason) { 346 case COMMIT_REASON_VIEW_COMMITTED: 347 return NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY; 348 case COMMIT_REASON_ACTIVITY_FINISHED: 349 return NOT_SHOWN_REASON_ACTIVITY_FINISHED; 350 case COMMIT_REASON_VIEW_CHANGED: 351 return NOT_SHOWN_REASON_VIEW_CHANGED; 352 case COMMIT_REASON_VIEW_CLICKED: 353 // TODO(b/234185326): Add separate reason for view clicked. 354 default: 355 return NOT_SHOWN_REASON_UNKNOWN; 356 } 357 } 358 getDisplayPresentationType(@iType int uiType)359 private static int getDisplayPresentationType(@UiType int uiType) { 360 switch (uiType) { 361 case UI_TYPE_MENU: 362 return AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU; 363 case UI_TYPE_INLINE: 364 return AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE; 365 case UI_TYPE_DIALOG: 366 return AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__DIALOG; 367 default: 368 return AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE; 369 } 370 } 371 } 372