diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index f9c44a94f89bc..749d6b6246d0b 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -24,6 +24,7 @@ import android.util.Slog; import com.android.internal.util.FrameworkStatsLog; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; +import com.android.server.credentials.metrics.BrowsedAuthenticationMetric; import com.android.server.credentials.metrics.CandidateAggregateMetric; import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric; import com.android.server.credentials.metrics.CandidatePhaseMetric; @@ -45,6 +46,7 @@ public class MetricUtilities { private static final String TAG = "MetricUtilities"; public static final String USER_CANCELED_SUBSTRING = "TYPE_USER_CANCELED"; + public static final int MIN_EMIT_WAIT_TIME_MS = 10; public static final int DEFAULT_INT_32 = -1; public static final String DEFAULT_STRING = ""; @@ -117,7 +119,8 @@ public class MetricUtilities { } /** - * A logging utility used primarily for the final phase of the current metric setup. + * A logging utility used primarily for the final phase of the current metric setup, focused on + * track 2, where the provider uid is known. * * @param finalPhaseMetric the coalesced data of the chosen provider * @param browsingPhaseMetrics the coalesced data of the browsing phase @@ -188,6 +191,86 @@ public class MetricUtilities { } } + /** + * This emits the authentication entry metrics for track 2, where the provider uid is known. + * + * @param authenticationMetric the authentication metric collection to emit with + * @param emitSequenceId an emitted sequence id for the current session + */ + public static void logApiCalledAuthenticationMetric( + BrowsedAuthenticationMetric authenticationMetric, + int emitSequenceId) { + try { + if (!LOG_FLAG) { + return; + } + FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_AUTH_CLICK_REPORTED, + /* session_id */ authenticationMetric.getSessionIdProvider(), + /* sequence_num */ emitSequenceId, + /* chosen_provider_uid */ authenticationMetric.getProviderUid(), + /* unique_response_classtypes */ + authenticationMetric.getAuthEntryCollective().getUniqueResponseStrings(), + /* per_classtype_counts */ + authenticationMetric.getAuthEntryCollective().getUniqueResponseCounts(), + /* unique_entries */ + authenticationMetric.getAuthEntryCollective().getUniqueEntries(), + /* auth_per_entry_counts */ + authenticationMetric.getAuthEntryCollective().getUniqueEntryCounts(), + /* framework_exception_unique_classtype */ + authenticationMetric.getFrameworkException(), + /* exception_specified */ authenticationMetric.isHasException(), + /* auth_provider_status */ + authenticationMetric.getProviderStatus(), + /* query_returned */ + authenticationMetric.isQueryReturned() + ); + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during candidate get metric logging: " + e); + } + } + + /** + * A logging utility used primarily for the candidate phase's get responses in the current + * metric setup. This helps avoid nested proto-files. This is primarily focused on track 2, + * where the provider uid is known. It ensures to run in a separate thread while emitting + * the multiple atoms to work with expected emit limits. + * + * @param providers a map with known providers and their held metric objects + * @param emitSequenceId an emitted sequence id for the current session, that matches the + * candidate emit value, as these metrics belong with the candidates + */ + public static void logApiCalledCandidateGetMetric(Map providers, + int emitSequenceId) { + try { + // TODO(b/274954697) : To queue format in future optimizations (metrics POC support) + if (!LOG_FLAG) { + return; + } + var sessions = providers.values(); + for (var session : sessions) { + try { + var metric = session.getProviderSessionMetric() + .getCandidatePhasePerProviderMetric(); + FrameworkStatsLog.write( + FrameworkStatsLog.CREDENTIAL_MANAGER_GET_REPORTED, + /* session_id */ metric.getSessionIdProvider(), + /* sequence_num */ emitSequenceId, + /* candidate_provider_uid */ metric.getCandidateUid(), + /* response_unique_classtypes */ + metric.getResponseCollective().getUniqueResponseStrings(), + /* per_classtype_counts */ + metric.getResponseCollective().getUniqueResponseCounts() + ); + } catch (Exception e) { + Slog.w(TAG, "Unexpected exception during get metric logging" + e); + } + } + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during candidate get metric logging: " + e); + } + } + + /** * A logging utility used primarily for the candidate phase of the current metric setup. This * will primarily focus on track 2, where the session id is associated with known providers, @@ -359,48 +442,57 @@ public class MetricUtilities { int sequenceNum) { try { if (!LOG_FLAG) { - FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_TOTAL_REPORTED, - /*session_id*/ candidateAggregateMetric.getSessionIdProvider(), - /*sequence_num*/ sequenceNum, - /*query_returned*/ candidateAggregateMetric.isQueryReturned(), - /*num_providers*/ candidateAggregateMetric.getNumProviders(), - /*min_query_start_timestamp_microseconds*/ - DEFAULT_INT_32, - /*max_query_end_timestamp_microseconds*/ - DEFAULT_INT_32, - /*query_response_unique_classtypes*/ - DEFAULT_REPEATED_STR, - /*query_per_classtype_counts*/ - DEFAULT_REPEATED_INT_32, - /*query_unique_entries*/ - DEFAULT_REPEATED_INT_32, - /*query_per_entry_counts*/ - DEFAULT_REPEATED_INT_32, - /*query_total_candidate_failure*/ - DEFAULT_INT_32, - /*query_framework_exception_unique_classtypes*/ - DEFAULT_REPEATED_STR, - /*query_per_exception_classtype_counts*/ - DEFAULT_REPEATED_INT_32, - /*auth_response_unique_classtypes*/ - DEFAULT_REPEATED_STR, - /*auth_per_classtype_counts*/ - DEFAULT_REPEATED_INT_32, - /*auth_unique_entries*/ - DEFAULT_REPEATED_INT_32, - /*auth_per_entry_counts*/ - DEFAULT_REPEATED_INT_32, - /*auth_total_candidate_failure*/ - DEFAULT_INT_32, - /*auth_framework_exception_unique_classtypes*/ - DEFAULT_REPEATED_STR, - /*auth_per_exception_classtype_counts*/ - DEFAULT_REPEATED_INT_32, - /*num_auth_clicks*/ - DEFAULT_INT_32, - /*auth_returned*/ false - ); + return; } + FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_TOTAL_REPORTED, + /*session_id*/ candidateAggregateMetric.getSessionIdProvider(), + /*sequence_num*/ sequenceNum, + /*query_returned*/ candidateAggregateMetric.isQueryReturned(), + /*num_query_providers*/ candidateAggregateMetric.getNumProviders(), + /*min_query_start_timestamp_microseconds*/ + DEFAULT_INT_32, + /*max_query_end_timestamp_microseconds*/ + DEFAULT_INT_32, + /*query_response_unique_classtypes*/ + candidateAggregateMetric.getAggregateCollectiveQuery() + .getUniqueResponseStrings(), + /*query_per_classtype_counts*/ + candidateAggregateMetric.getAggregateCollectiveQuery() + .getUniqueResponseCounts(), + /*query_unique_entries*/ + candidateAggregateMetric.getAggregateCollectiveQuery() + .getUniqueEntries(), + /*query_per_entry_counts*/ + candidateAggregateMetric.getAggregateCollectiveQuery() + .getUniqueEntryCounts(), + /*query_total_candidate_failure*/ + DEFAULT_INT_32, + /*query_framework_exception_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*query_per_exception_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*auth_response_unique_classtypes*/ + candidateAggregateMetric.getAggregateCollectiveAuth() + .getUniqueResponseStrings(), + /*auth_per_classtype_counts*/ + candidateAggregateMetric.getAggregateCollectiveAuth() + .getUniqueResponseCounts(), + /*auth_unique_entries*/ + candidateAggregateMetric.getAggregateCollectiveAuth() + .getUniqueEntries(), + /*auth_per_entry_counts*/ + candidateAggregateMetric.getAggregateCollectiveAuth() + .getUniqueEntryCounts(), + /*auth_total_candidate_failure*/ + DEFAULT_INT_32, + /*auth_framework_exception_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*auth_per_exception_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*num_auth_clicks*/ + candidateAggregateMetric.getNumAuthEntriesTapped(), + /*auth_returned*/ false + ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during metric logging: " + e); } diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 51af25b589924..14260f0e1ad43 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -269,6 +269,7 @@ public final class ProviderGetSession extends ProviderSession implements CredentialManagerUi.Credential Slog.w(TAG, "providerSession not found in onUiSelection. This is strange."); return; } + ProviderSessionMetric providerSessionMetric = providerSession.mProviderSessionMetric; + int initialAuthMetricsProvider = providerSessionMetric.getBrowsedAuthenticationMetric() + .size(); mRequestSessionMetric.collectMetricPerBrowsingSelect(selection, providerSession.mProviderSessionMetric.getCandidatePhasePerProviderMetric()); providerSession.onUiEntrySelected(selection.getEntryKey(), selection.getEntrySubkey(), selection.getPendingIntentProviderResponse()); + int numAuthPerProvider = providerSessionMetric.getBrowsedAuthenticationMetric().size(); + boolean authMetricLogged = (numAuthPerProvider - initialAuthMetricsProvider) == 1; + if (authMetricLogged) { + mRequestSessionMetric.logAuthEntry( + providerSession.mProviderSessionMetric.getBrowsedAuthenticationMetric() + .get(numAuthPerProvider - 1)); + } } protected void finishSession(boolean propagateCancellation) { diff --git a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java index 51e86d51acdfd..98ea5b977a925 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java @@ -16,13 +16,34 @@ package com.android.server.credentials.metrics; +import com.android.server.credentials.metrics.shared.ResponseCollective; + +import java.util.Map; + /** * Encapsulates an authentication entry click atom, as a part of track 2. * Contains information about what was collected from the authentication entry output. */ public class BrowsedAuthenticationMetric { + private static final String TAG = "BrowsedAuthenticationMetric"; // The session id of this provider known flow related metric private final int mSessionIdProvider; + + // The provider associated with the press, defaults to -1 + private int mProviderUid = -1; + + // The response objects collected for this authentication entry click, default empty + private ResponseCollective mAuthEntryCollective = new ResponseCollective(Map.of(), Map.of()); + + // Indicates if an exception was thrown by this provider, false by default + private boolean mHasException = false; + // Indicates the framework only exception belonging to this provider, defaults to empty string + private String mFrameworkException = ""; + // The status of this particular provider + private int mProviderStatus = -1; + // Indicates if this provider returned from the authentication entry query, default false + private boolean mQueryReturned = false; + // TODO(b/271135048) - Match the atom and provide a clean per provider session metric // encapsulation. @@ -33,4 +54,53 @@ public class BrowsedAuthenticationMetric { public int getSessionIdProvider() { return mSessionIdProvider; } + + public void setProviderUid(int providerUid) { + mProviderUid = providerUid; + } + + public int getProviderUid() { + return mProviderUid; + } + + public void setAuthEntryCollective( + ResponseCollective authEntryCollective) { + this.mAuthEntryCollective = authEntryCollective; + } + + public ResponseCollective getAuthEntryCollective() { + return mAuthEntryCollective; + } + + public void setHasException(boolean hasException) { + mHasException = hasException; + } + + public void setFrameworkException(String frameworkException) { + mFrameworkException = frameworkException; + } + + public void setProviderStatus(int providerStatus) { + mProviderStatus = providerStatus; + } + + public void setQueryReturned(boolean queryReturned) { + mQueryReturned = queryReturned; + } + + public boolean isQueryReturned() { + return mQueryReturned; + } + + public int getProviderStatus() { + return mProviderStatus; + } + + public String getFrameworkException() { + return mFrameworkException; + } + + public boolean isHasException() { + return mHasException; + } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java index 08e75837a2748..05d048d0e79ec 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java @@ -17,7 +17,9 @@ package com.android.server.credentials.metrics; import com.android.server.credentials.ProviderSession; +import com.android.server.credentials.metrics.shared.ResponseCollective; +import java.util.LinkedHashMap; import java.util.Map; /** @@ -29,12 +31,22 @@ public class CandidateAggregateMetric { private static final String TAG = "CandidateProviderMetric"; // The session id of this provider metric private final int mSessionIdProvider; - // Indicates if this provider returned from the query phase, default false + // Indicates if this provider returned from the candidate query phase, + // true if at least one provider returns validly, even if empty, default false private boolean mQueryReturned = false; // Indicates the total number of providers this aggregate captures information for, default 0 private int mNumProviders = 0; + // Indicates if the authentication entry returned, true if at least one entry returns validly, + // even if empty, default false + private boolean mAuthReturned = false; // Indicates the total number of authentication entries that were tapped in aggregate, default 0 private int mNumAuthEntriesTapped = 0; + // The combined aggregate collective across the candidate get/create + private ResponseCollective mAggregateCollectiveQuery = + new ResponseCollective(Map.of(), Map.of()); + // The combined aggregate collective across the auth entry info + private ResponseCollective mAggregateCollectiveAuth = + new ResponseCollective(Map.of(), Map.of()); public CandidateAggregateMetric(int sessionIdTrackOne) { mSessionIdProvider = sessionIdTrackOne; @@ -51,14 +63,47 @@ public class CandidateAggregateMetric { * @param providers the providers associated with the candidate flow */ public void collectAverages(Map providers) { - // TODO(b/271135048) : Complete this method + collectQueryAggregates(providers); + collectAuthAggregates(providers); + } + + private void collectQueryAggregates(Map providers) { mNumProviders = providers.size(); + Map responseCountQuery = new LinkedHashMap<>(); + Map entryCountQuery = new LinkedHashMap<>(); var providerSessions = providers.values(); for (var session : providerSessions) { - var metric = session.getProviderSessionMetric(); - mQueryReturned = mQueryReturned || metric - .mCandidatePhasePerProviderMetric.isQueryReturned(); + var sessionMetric = session.getProviderSessionMetric(); + var candidateMetric = sessionMetric.getCandidatePhasePerProviderMetric(); + mQueryReturned = mQueryReturned || candidateMetric.isQueryReturned(); + ResponseCollective candidateCollective = candidateMetric.getResponseCollective(); + ResponseCollective.combineTypeCountMaps(responseCountQuery, + candidateCollective.getResponseCountsMap()); + ResponseCollective.combineTypeCountMaps(entryCountQuery, + candidateCollective.getEntryCountsMap()); } + mAggregateCollectiveQuery = new ResponseCollective(responseCountQuery, entryCountQuery); + } + + private void collectAuthAggregates(Map providers) { + mNumProviders = providers.size(); + Map responseCountAuth = new LinkedHashMap<>(); + Map entryCountAuth = new LinkedHashMap<>(); + var providerSessions = providers.values(); + for (var session : providerSessions) { + var sessionMetric = session.getProviderSessionMetric(); + var authMetrics = sessionMetric.getBrowsedAuthenticationMetric(); + mNumAuthEntriesTapped += authMetrics.size(); + for (var authMetric : authMetrics) { + mAuthReturned = mAuthReturned || authMetric.isQueryReturned(); + ResponseCollective authCollective = authMetric.getAuthEntryCollective(); + ResponseCollective.combineTypeCountMaps(responseCountAuth, + authCollective.getResponseCountsMap()); + ResponseCollective.combineTypeCountMaps(entryCountAuth, + authCollective.getEntryCountsMap()); + } + } + mAggregateCollectiveAuth = new ResponseCollective(responseCountAuth, entryCountAuth); } public int getNumProviders() { @@ -69,7 +114,20 @@ public class CandidateAggregateMetric { return mQueryReturned; } + public int getNumAuthEntriesTapped() { return mNumAuthEntriesTapped; } + + public ResponseCollective getAggregateCollectiveQuery() { + return mAggregateCollectiveQuery; + } + + public ResponseCollective getAggregateCollectiveAuth() { + return mAggregateCollectiveAuth; + } + + public boolean isAuthReturned() { + return mAuthReturned; + } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java index 47db8f59ff355..629b2dfe55878 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java @@ -28,6 +28,7 @@ import android.util.Slog; import com.android.server.credentials.MetricUtilities; import com.android.server.credentials.metrics.shared.ResponseCollective; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -47,13 +48,17 @@ public class ProviderSessionMetric { protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric; // IFF there was an authentication entry clicked, this stores all required information for - // that event. This is for the 'get' flow. + // that event. This is for the 'get' flow. Notice these flows may be repetitive. + // Thus each provider stores a list of authentication metrics. The time between emits + // of these metrics should exceed 10 ms (given human reaction time is ~ 100's of ms), so emits + // will never collide. However, for aggregation, this will store information accordingly. @NonNull - protected final BrowsedAuthenticationMetric mBrowsedAuthenticationMetric; + protected final List mBrowsedAuthenticationMetric = + new ArrayList<>(); public ProviderSessionMetric(int sessionIdTrackTwo) { mCandidatePhasePerProviderMetric = new CandidatePhaseMetric(sessionIdTrackTwo); - mBrowsedAuthenticationMetric = new BrowsedAuthenticationMetric(sessionIdTrackTwo); + mBrowsedAuthenticationMetric.add(new BrowsedAuthenticationMetric(sessionIdTrackTwo)); } /** @@ -63,6 +68,12 @@ public class ProviderSessionMetric { return mCandidatePhasePerProviderMetric; } + /** + * Retrieves the authentication clicked metric information. + */ + public List getBrowsedAuthenticationMetric() { + return mBrowsedAuthenticationMetric; + } /** * This collects for ProviderSessions, with respect to the candidate providers, whether @@ -71,7 +82,30 @@ public class ProviderSessionMetric { * @param hasException indicates if the candidate provider associated with an exception */ public void collectCandidateExceptionStatus(boolean hasException) { - mCandidatePhasePerProviderMetric.setHasException(hasException); + try { + mCandidatePhasePerProviderMetric.setHasException(hasException); + } catch (Exception e) { + Slog.i(TAG, "Error while setting candidate metric exception " + e); + } + } + + /** + * This collects for ProviderSessions, with respect to the authentication entry provider, + * if an exception occurred in the authentication entry click. It's expected that these + * collections always occur after at least 1 authentication metric has been collected + * for the provider associated with this metric encapsulation. + * + * @param hasException indicates if the candidate provider from an authentication entry + * associated with an exception + */ + public void collectAuthenticationExceptionStatus(boolean hasException) { + try { + var mostRecentAuthenticationMetric = mBrowsedAuthenticationMetric + .get(mBrowsedAuthenticationMetric.size() - 1); + mostRecentAuthenticationMetric.setHasException(hasException); + } catch (Exception e) { + Slog.i(TAG, "Error while setting authentication metric exception " + e); + } } /** @@ -91,6 +125,21 @@ public class ProviderSessionMetric { // TODO(b/271135048) - Mimic typical candidate update, but with authentication metric // Collect the final timestamps (and start timestamp), status, exceptions and the provider // uid. This occurs typically *after* the collection is complete. + var mostRecentAuthenticationMetric = mBrowsedAuthenticationMetric + .get(mBrowsedAuthenticationMetric.size() - 1); + mostRecentAuthenticationMetric.setProviderUid(providerSessionUid); + // TODO(immediately) - add timestamps (no longer needed!!) but also update below values! + if (isFailureStatus) { + mostRecentAuthenticationMetric.setQueryReturned(false); + mostRecentAuthenticationMetric.setProviderStatus( + ProviderStatusForMetrics.QUERY_FAILURE + .getMetricCode()); + } else if (isCompletionStatus) { + mostRecentAuthenticationMetric.setQueryReturned(true); + mostRecentAuthenticationMetric.setProviderStatus( + ProviderStatusForMetrics.QUERY_SUCCESS + .getMetricCode()); + } } /** @@ -197,6 +246,17 @@ public class ProviderSessionMetric { mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective); } + /** + * This sets up an authentication metric collector to the flow. This must be called before + * any logical edits are done in a new authentication entry metric collection. + */ + public void createAuthenticationBrowsingMetric() { + BrowsedAuthenticationMetric browsedAuthenticationMetric = + new BrowsedAuthenticationMetric(mCandidatePhasePerProviderMetric + .getSessionIdProvider()); + mBrowsedAuthenticationMetric.add(browsedAuthenticationMetric); + } + private void beginCreateCredentialResponseCollectionCandidateEntryMetrics( BeginCreateCredentialResponse response) { Map entryCounts = new LinkedHashMap<>(); @@ -240,7 +300,10 @@ public class ProviderSessionMetric { if (!isAuthEntry) { mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective); } else { - // TODO(b/immediately) - Add the auth entry get logic + // The most recent auth entry must be created already + var browsedAuthenticationMetric = + mBrowsedAuthenticationMetric.get(mBrowsedAuthenticationMetric.size() - 1); + browsedAuthenticationMetric.setAuthEntryCollective(responseCollective); } } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java index 03ffe23f98866..009cfa852bf40 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -16,9 +16,12 @@ package com.android.server.credentials.metrics; +import static com.android.server.credentials.MetricUtilities.DEFAULT_INT_32; import static com.android.server.credentials.MetricUtilities.DELTA_EXCEPTION_CUT; import static com.android.server.credentials.MetricUtilities.DELTA_RESPONSES_CUT; import static com.android.server.credentials.MetricUtilities.generateMetricKey; +import static com.android.server.credentials.MetricUtilities.logApiCalledAuthenticationMetric; +import static com.android.server.credentials.MetricUtilities.logApiCalledCandidateGetMetric; import static com.android.server.credentials.MetricUtilities.logApiCalledCandidatePhase; import static com.android.server.credentials.MetricUtilities.logApiCalledFinalPhase; import static com.android.server.credentials.MetricUtilities.logApiCalledNoUidFinal; @@ -52,7 +55,6 @@ public class RequestSessionMetric { protected final InitialPhaseMetric mInitialPhaseMetric; protected final ChosenProviderFinalPhaseMetric mChosenProviderFinalPhaseMetric; - // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4) protected List mCandidateBrowsingPhaseMetric = new ArrayList<>(); // Specific aggregate candidate provider metric for the provider this session handles @NonNull @@ -100,8 +102,6 @@ public class RequestSessionMetric { * @param timestampStarted the timestamp the service begins at * @param mCallingUid the calling process's uid * @param metricCode typically pulled from {@link ApiName} - * @param callingAppFlowUniqueInt the unique integer used as the session id for the calling app - * known flow */ public void collectInitialPhaseMetricInfo(long timestampStarted, int mCallingUid, int metricCode) { @@ -214,9 +214,8 @@ public class RequestSessionMetric { /** * During browsing, where multiple entries can be selected, this collects the browsing phase - * metric information. - * TODO(b/271135048) - modify asap to account for a new metric emit per browse response to - * framework. + * metric information. This is emitted together with the final phase, and the recursive path + * with authentication entries, which may occur in rare circumstances, are captured. * * @param selection contains the selected entry key type * @param selectedProviderPhaseMetric contains the utility information of the selected provider @@ -343,6 +342,7 @@ public class RequestSessionMetric { public void logCandidatePhaseMetrics(Map providers) { try { logApiCalledCandidatePhase(providers, ++mSequenceCounter, mInitialPhaseMetric); + logApiCalledCandidateGetMetric(providers, mSequenceCounter); } catch (Exception e) { Slog.i(TAG, "Unexpected error during candidate metric emit: " + e); } @@ -362,6 +362,28 @@ public class RequestSessionMetric { } } + /** + * This logs the authentication entry when browsed. Combined with the known browsed clicks + * in the {@link ChosenProviderFinalPhaseMetric}, this fully captures the authentication entry + * logic for multiple loops. An auth entry may have default or missing data, but if a provider + * was never assigned to an auth entry, this indicates an auth entry was never clicked. + * This case is handled in this emit. + * + * @param browsedAuthenticationMetric the authentication metric information to emit + */ + public void logAuthEntry(BrowsedAuthenticationMetric browsedAuthenticationMetric) { + try { + if (browsedAuthenticationMetric.getProviderUid() == DEFAULT_INT_32) { + Slog.v(TAG, "An authentication entry was not clicked"); + return; + } + logApiCalledAuthenticationMetric(browsedAuthenticationMetric, ++mSequenceCounter); + } catch (Exception e) { + Slog.i(TAG, "Unexpected error during metric logging: " + e); + } + + } + /** * Handles the final logging for RequestSession context for the final phase. * diff --git a/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java b/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java index fd785c2f4dfcd..0958a841fe7ee 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java +++ b/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import com.android.server.credentials.metrics.EntryEnum; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -69,6 +70,24 @@ public class ResponseCollective { return result; } + /** + * Returns an unmodifiable map of the entry counts, safe under the immutability of the + * class the original map is held within. + * @return an unmodifiable map of the entry : counts + */ + public Map getEntryCountsMap() { + return Collections.unmodifiableMap(mEntryCounts); + } + + /** + * Returns an unmodifiable map of the response counts, safe under the immutability of the + * class the original map is held within. + * @return an unmodifiable map of the response : counts + */ + public Map getResponseCountsMap() { + return Collections.unmodifiableMap(mResponseCounts); + } + /** * Returns the unique, deduped, response classtype counts for logging associated with this * provider. @@ -112,4 +131,44 @@ public class ResponseCollective { public int getNumEntriesTotal() { return mEntryCounts.values().stream().mapToInt(Integer::intValue).sum(); } + + /** + * This combines the current collective with another collective, only if that other + * collective is indeed a differing one in memory. + * @param other the other response collective to combine with + * @return a combined {@link ResponseCollective} object + */ + public ResponseCollective combineCollectives(ResponseCollective other) { + if (this == other) { + return this; + } + + Map responseCounts = new LinkedHashMap<>(other.mResponseCounts); + for (String response : mResponseCounts.keySet()) { + responseCounts.merge(response, mResponseCounts.get(response), Integer::sum); + } + + Map entryCounts = new LinkedHashMap<>(other.mEntryCounts); + for (EntryEnum entry : mEntryCounts.keySet()) { + entryCounts.merge(entry, mEntryCounts.get(entry), Integer::sum); + } + + return new ResponseCollective(responseCounts, entryCounts); + } + + /** + * Given two maps of type : counts, this combines the second into the first, to get an aggregate + * deduped type:count output. + * @param first the first map of some type to counts used as the base + * @param second the second map of some type to counts that mixies with the first + * @param The type of the object we are mix-deduping - i.e. responses or entries. + * @return the first map updated with the second map's information for type:counts + */ + public static Map combineTypeCountMaps(Map first, + Map second) { + for (T response : second.keySet()) { + first.merge(response, first.getOrDefault(response, 0), Integer::sum); + } + return first; + } }