7979import com .microsoft .identity .common .java .ui .webview .authorization .IAuthorizationCompletionCallback ;
8080import com .microsoft .identity .common .java .challengehandlers .PKeyAuthChallenge ;
8181import com .microsoft .identity .common .java .challengehandlers .PKeyAuthChallengeFactory ;
82+ import com .microsoft .identity .common .internal .telemetry .OnboardingTelemetryRecorder ;
8283import com .microsoft .identity .common .internal .ui .webview .challengehandlers .PKeyAuthChallengeHandler ;
8384import com .microsoft .identity .common .java .WarningType ;
8485import com .microsoft .identity .common .java .exception .ClientException ;
8586import com .microsoft .identity .common .java .exception .ErrorStrings ;
8687import com .microsoft .identity .common .java .providers .RawAuthorizationResult ;
88+ import static com .microsoft .identity .common .java .telemetry .OnboardingTelemetryConstants .STEP_AUTHENTICATOR_MFA_LINKING_STARTED ;
89+ import static com .microsoft .identity .common .java .telemetry .OnboardingTelemetryConstants .STEP_BROKER_INSTALL_PROMPTED ;
90+ import static com .microsoft .identity .common .java .telemetry .OnboardingTelemetryConstants .STEP_COMPANY_PORTAL_LAUNCHED ;
91+ import static com .microsoft .identity .common .java .telemetry .OnboardingTelemetryConstants .STEP_GOOGLE_ENROLLMENT_STARTED ;
92+ import static com .microsoft .identity .common .java .telemetry .OnboardingTelemetryConstants .STEP_MDM_ENROLLMENT_STARTED ;
93+ import static com .microsoft .identity .common .java .telemetry .OnboardingTelemetryConstants .STEP_WEB_CP_ENROLLMENT_STARTED ;
8794import com .microsoft .identity .common .java .util .StringUtil ;
8895import com .microsoft .identity .common .logging .Logger ;
8996
@@ -154,6 +161,16 @@ public class AzureActiveDirectoryWebViewClient extends OAuth2WebViewClient {
154161
155162 private String mPasskeyRegistrationScript ;
156163
164+ /**
165+ * Optional onboarding telemetry recorder. Set via {@link #setOnboardingTelemetryRecorder}
166+ * after this client is constructed (the recorder is created by the host fragment/activity
167+ * when a seed JSON arrives, which is typically later than WebView construction).
168+ * When non-null, key URL transitions (broker install, MDM enrollment, Company Portal
169+ * launch, etc.) and {@code lastLoadedDomain} are recorded for the onboarding telemetry blob.
170+ */
171+ @ Nullable
172+ private OnboardingTelemetryRecorder mOnboardingTelemetryRecorder ;
173+
157174 /**
158175 * Callback for tracking URL load events.
159176 */
@@ -201,11 +218,28 @@ public void initializeAuthUxJavaScriptApi(@NonNull final WebView view, final Str
201218 }
202219 }
203220
221+ /**
222+ * Attach an onboarding telemetry recorder so subsequent WebView page transitions
223+ * (broker install prompts, MDM enrollment redirects, Company Portal launches, etc.)
224+ * are recorded into the onboarding telemetry blob.
225+ *
226+ * Recorder is owned by the host fragment / activity (e.g. OneAuthNavigationFragment
227+ * on the OneAuth side, AuthorizationActivity on the broker side). May be null when
228+ * no seed JSON is available — in which case all hooks become no-ops.
229+ */
230+ public void setOnboardingTelemetryRecorder (
231+ @ Nullable final OnboardingTelemetryRecorder recorder ) {
232+ mOnboardingTelemetryRecorder = recorder ;
233+ }
234+
204235 @ Override
205236 public void onPageFinished (final WebView view ,
206237 final String url ) {
207238 super .onPageFinished (view , url );
208239
240+ // Onboarding telemetry: record domain navigation (best-effort, no-op if no recorder).
241+ recordLastLoadedDomain (url );
242+
209243 if (mAuthUxJavaScriptInterfaceAdded ) {
210244 // Add a function to the api. Must do this to first stringify the dict object, as Android @JavaScriptInterface does not support
211245 // passing dict objects through Javascript APIs, only Strings and primitive types. Server side will be sending message in a dict
@@ -722,6 +756,9 @@ private void processDeviceCaRequest(@NonNull final WebView view, @NonNull final
722756 final String methodTag = TAG + ":processDeviceCaRequest" ;
723757 Logger .info (methodTag , "This is a device CA request." );
724758
759+ // Onboarding telemetry: device CA blocking redirect → MDM enrollment phase.
760+ recordOnboardingStep (STEP_MDM_ENROLLMENT_STARTED );
761+
725762 if (shouldLaunchCompanyPortal ()) {
726763 // If CP is installed, redirect to CP.
727764 // TODO: Until we get a signal from eSTS that CP is the MDM app, we cannot assume that.
@@ -831,6 +868,8 @@ private String getHomeTenantIdFromUrl(@NonNull final String url) {
831868 // This is a special case where the enrollment is not done in the WebView, but rather in the browser.
832869 private void processWebCpEnrollmentUrl (@ NonNull final WebView view , @ NonNull final String url ) {
833870 final String methodTag = TAG + ":processWebCpEnrollmentUrl" ;
871+ // Onboarding telemetry: WebCP enrollment is a distinct enrollment path.
872+ recordOnboardingStep (STEP_WEB_CP_ENROLLMENT_STARTED );
834873 final Span span = createSpanWithAttributesFromParent (SpanName .ProcessWebCpEnrollmentRedirect .name ());
835874 try (final Scope scope = SpanExtension .makeCurrentSpan (span )) {
836875 view .stopLoading ();
@@ -859,6 +898,8 @@ public void run() {
859898 // Opens the Google enrollment URL in the browser or the default intent handler (like DPC)
860899 private void openGoogleEnrollmentUrl (@ NonNull final String url ) {
861900 final String methodTag = TAG + ":openGoogleEnrollmentUrl" ;
901+ // Onboarding telemetry: Google enrollment redirect is a distinct enrollment path.
902+ recordOnboardingStep (STEP_GOOGLE_ENROLLMENT_STARTED );
862903 Logger .info (methodTag , "Opening Google enrollment URL" );
863904 try {
864905 final Intent intent = new Intent (Intent .ACTION_VIEW , Uri .parse (url ));
@@ -883,6 +924,7 @@ private boolean processPlayStoreURL(@NonNull final WebView view, @NonNull final
883924 }
884925 final String appPackageName = getBrokerAppPackageNameFromUrl (url );
885926 Logger .info (methodTag , "Request to open PlayStore to install package : '" + appPackageName + "'" );
927+ recordOnboardingStep (STEP_BROKER_INSTALL_PROMPTED );
886928
887929 try {
888930 final Intent intent = new Intent (Intent .ACTION_VIEW , Uri .parse (PLAY_STORE_INSTALL_PREFIX + appPackageName ));
@@ -902,6 +944,7 @@ private boolean processPlayStoreURLForBrokerApps(@NonNull final WebView view, @N
902944
903945 final String appPackageName = getBrokerAppPackageNameFromUrl (url );
904946 Logger .info (methodTag , "Request to open PlayStore to install package : '" + appPackageName + "'" );
947+ recordOnboardingStep (STEP_BROKER_INSTALL_PROMPTED );
905948
906949 try {
907950 final Intent intent = new Intent (Intent .ACTION_VIEW , Uri .parse (PLAY_STORE_INSTALL_APP_PREFIX + appPackageName ));
@@ -920,6 +963,8 @@ private boolean processPlayStoreURLForBrokerApps(@NonNull final WebView view, @N
920963
921964 private void processAuthAppMFAUrl (String url ) {
922965 final String methodTag = TAG + ":processAuthAppMFAUrl" ;
966+ // Onboarding telemetry: redirect to Authenticator for MFA linking.
967+ recordOnboardingStep (STEP_AUTHENTICATOR_MFA_LINKING_STARTED );
923968 Logger .verbose (methodTag , "Linking Account in Broker for MFA." );
924969 try {
925970 final Intent intent = new Intent (Intent .ACTION_VIEW , Uri .parse (url ));
@@ -964,6 +1009,9 @@ void processAuthenticatorActivationAppLink(@NonNull final WebView view,
9641009 private void launchCompanyPortal () {
9651010 final String methodTag = TAG + ":launchCompanyPortal" ;
9661011
1012+ // Onboarding telemetry: Company Portal launch is a discrete onboarding step.
1013+ recordOnboardingStep (STEP_COMPANY_PORTAL_LAUNCHED );
1014+
9671015 Logger .verbose (methodTag , "Sending intent to launch the CompanyPortal." );
9681016 final Intent intent = new Intent ();
9691017 intent .setComponent (new ComponentName (
@@ -1051,6 +1099,10 @@ private void processWebCpRequest(@NonNull final WebView view, @NonNull final Str
10511099 private void processInstallRequest (@ NonNull final WebView view , @ NonNull final String url ) {
10521100 final String methodTag = TAG + ":processInstallRequest" ;
10531101
1102+ // Onboarding telemetry: broker install request reached the WebView client. Record the
1103+ // step at method entry so we capture intent regardless of the parsed result code.
1104+ recordOnboardingStep (STEP_BROKER_INSTALL_PROMPTED );
1105+
10541106 final RawAuthorizationResult result = RawAuthorizationResult .fromRedirectUri (url );
10551107
10561108 if (result .getResultCode () != RawAuthorizationResult .ResultCode .BROKER_INSTALLATION_TRIGGERED ) {
@@ -1108,6 +1160,8 @@ private void processInvalidRedirectUri(@NonNull final WebView view,
11081160 */
11091161 private void processIntentToInstallBrokerApp (@ NonNull final WebView view , @ NonNull final String intentUrl ) {
11101162 final String methodTag = TAG + ":processIntentToInstallBrokerApp" ;
1163+ // Onboarding telemetry: alternate broker install path (intent-scheme).
1164+ recordOnboardingStep (STEP_BROKER_INSTALL_PROMPTED );
11111165 try {
11121166 final Intent intent = Intent .parseUri (intentUrl , Intent .URI_INTENT_SCHEME );
11131167 if (intent != null && intent .getPackage () != null ) {
@@ -1430,4 +1484,42 @@ private Span createSpanWithAttributesFromParent(@NonNull final String spanName)
14301484 public void addPasskeyRegistrationJsScript (@ NonNull final String script ) {
14311485 this .mPasskeyRegistrationScript = script ;
14321486 }
1487+
1488+ /**
1489+ * Best-effort onboarding telemetry hook: records a step on the attached recorder
1490+ * if one is present. No-op when no recorder has been attached. Never throws.
1491+ */
1492+ private void recordOnboardingStep (@ NonNull final String stepId ) {
1493+ final OnboardingTelemetryRecorder recorder = mOnboardingTelemetryRecorder ;
1494+ if (recorder == null ) {
1495+ return ;
1496+ }
1497+ try {
1498+ recorder .addStep (stepId );
1499+ } catch (final Throwable t ) {
1500+ Logger .warn (TAG , "Onboarding telemetry: failed to record step " + stepId + ": " + t .getMessage ());
1501+ }
1502+ }
1503+
1504+ /**
1505+ * Best-effort onboarding telemetry hook: records the host of the most recently loaded
1506+ * page on the attached recorder. No-op when no recorder is attached or the URL has no
1507+ * extractable host. Never throws.
1508+ */
1509+ private void recordLastLoadedDomain (@ NonNull final String url ) {
1510+ final OnboardingTelemetryRecorder recorder = mOnboardingTelemetryRecorder ;
1511+ if (recorder == null || url .isEmpty ()) {
1512+ return ;
1513+ }
1514+ try {
1515+ final String host = Uri .parse (url ).getHost ();
1516+ if (host != null && !host .isEmpty ()) {
1517+ recorder .setLastLoadedDomain (host );
1518+ } else {
1519+ Logger .verbose (TAG , "Onboarding telemetry: no host extracted from URL" );
1520+ }
1521+ } catch (final Throwable t ) {
1522+ Logger .warn (TAG , "Onboarding telemetry: failed to record last loaded domain: " + t .getMessage ());
1523+ }
1524+ }
14331525}
0 commit comments