From c58fb41ed695a28f293e5e6c02b53373176bd2fe Mon Sep 17 00:00:00 2001 From: p3dr0rv Date: Thu, 28 Aug 2025 23:31:17 -0700 Subject: [PATCH 01/11] prototype --- .../oauth2/AuthorizationActivityFactory.kt | 1 - .../internal/providers/oauth2/DUNAActivity.kt | 56 +++++++++++++++++++ .../oauth2/WebViewAuthorizationFragment.java | 17 +++--- .../SwitchBrowserRequestHandler.kt | 7 ++- .../SwitchBrowserProtocolCoordinator.kt | 4 +- 5 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt index da7cc5140a..5424a3c98e 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt @@ -66,7 +66,6 @@ object AuthorizationActivityFactory { if (ProcessUtil.isBrokerProcess(parameters.context)) { intent = Intent(parameters.context, BrokerAuthorizationActivity::class.java) if (parameters.requestUrl.contains(AuthenticationConstants.SWITCH_BROWSER.CLIENT_SUPPORTS_FLOW)) { - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) // In the case of a SwitchBrowser protocol, we need to transition from the browser to the WebView. // These flags ensure that we have a new task stack that allows for this transition. } diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt new file mode 100644 index 0000000000..96c47fcc32 --- /dev/null +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt @@ -0,0 +1,56 @@ +package com.microsoft.identity.common.internal.providers.oauth2 + +import android.content.Intent +import android.os.Bundle +import androidx.fragment.app.FragmentActivity +import com.microsoft.identity.common.internal.ui.webview.switchbrowser.SwitchBrowserProtocolCoordinator +import com.microsoft.identity.common.logging.Logger + +class DUNAActivity: FragmentActivity() { + + companion object { + private val TAG: String = DUNAActivity::class.java.simpleName + } + + + override fun onCreate(savedInstanceState: Bundle?) { + val methodTag = "$TAG:onCreate" + super.onCreate(savedInstanceState) + + + Logger.info(methodTag, "DUNAActivity onCreate") + if (savedInstanceState == null) { + Logger.info(methodTag, "Extracting browser intent from extras for DUNA flow") + if (intent == null) { + Logger.warn(methodTag, "Intent is null") + finish() + return + } + val browser = intent.getParcelableExtra("browser_intent") + if (browser == null) { + Logger.warn(methodTag, "No browser intent found in extras") + } + Logger.info(methodTag, "Launching Custom Chrome Tab intent for DUNA authentication") + startActivity(browser) + } else { + Logger.info(methodTag, "Activity restored from saved state - Skipping browser intent launch") + } + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + //setIntent(intent) + val methodTag = "$TAG:onNewIntent" + Logger.info(methodTag, "onNewIntent") + + intent?.dataString?.let { intentData -> + val result = SwitchBrowserProtocolCoordinator.getIntentToResumeWebViewAuth(applicationContext, intentData) + Logger.info(methodTag, intentData) + WebViewAuthorizationFragment.dunaIntent = result + } + finishAndRemoveTask() + } + + + +} \ No newline at end of file diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java index 91cbacc1cd..25df968e1f 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java @@ -80,6 +80,7 @@ import java.net.URLEncoder; import java.util.Arrays; import java.util.HashMap; +import java.util.Objects; import static com.microsoft.identity.common.java.AuthenticationConstants.OAuth2.UTID; @@ -129,10 +130,13 @@ public class WebViewAuthorizationFragment extends AuthorizationFragment { private boolean isBrokerRequest = false; + public static Intent dunaIntent = null; + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); final String methodTag = TAG + ":onCreate"; + Logger.info(methodTag, "WebViewAuthorizationFragment onCreate"); final FragmentActivity activity = getActivity(); if (activity != null) { WebViewUtil.setDataDirectorySuffix(activity.getApplicationContext()); @@ -151,8 +155,11 @@ public void onCreate(@Nullable Bundle savedInstanceState) { @Override public void onResume() { super.onResume(); + Logger.info(TAG + ":onResume", "WebViewAuthorizationFragment onResume"); + if (getSwitchBrowserCoordinator().isExpectingSwitchBrowserResume()) { resumeSwitchBrowser(getExtras()); + dunaIntent = new Intent(); } } @@ -163,15 +170,11 @@ public void onResume() { */ @NonNull private Bundle getExtras() { - final Activity activity = getActivity(); - if (activity == null) { - return Bundle.EMPTY; - } - final Intent intent = activity.getIntent(); - if (intent == null) { + if (dunaIntent == null) { return Bundle.EMPTY; } - final Bundle extras = intent.getExtras(); + final Bundle extras = dunaIntent.getExtras(); + dunaIntent = null; return extras == null ? Bundle.EMPTY : extras; } diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandler.kt b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandler.kt index df6f78268a..263f7fc349 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandler.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandler.kt @@ -26,6 +26,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import com.microsoft.identity.common.adal.internal.AuthenticationConstants.SWITCH_BROWSER +import com.microsoft.identity.common.internal.providers.oauth2.DUNAActivity import com.microsoft.identity.common.internal.ui.browser.AndroidBrowserSelector import com.microsoft.identity.common.internal.ui.browser.CustomTabsManager import com.microsoft.identity.common.internal.ui.webview.switchbrowser.SwitchBrowserUriHelper @@ -130,7 +131,11 @@ class SwitchBrowserRequestHandler( ) browserIntent.setPackage(browser.packageName) browserIntent.setData(switchBrowserChallenge.processUri) - activity.startActivity(browserIntent) + val dunaIntent = Intent(activity, DUNAActivity::class.java) + dunaIntent.putExtra("browser_intent", browserIntent) + dunaIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + activity.startActivity(dunaIntent) + //activity.startActivity(browserIntent) isChallengeHandled = true span.setAttribute( AttributeName.is_switch_browser_request_handled.name, diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt index 5a41ff9236..c358663b28 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt @@ -83,7 +83,9 @@ class SwitchBrowserProtocolCoordinator( // Ensures that if the activity is already running at the top of the stack (WebView), // a new instance is not created, but its existing instance is brought to the foreground // instead of launching a new one. - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + + //intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) intent.putExtra( SWITCH_BROWSER.ACTION_URI, uri.getQueryParameter(SWITCH_BROWSER.ACTION_URI) From 7422790726e6d094833011207e6408b6e1f3af55 Mon Sep 17 00:00:00 2001 From: p3dr0rv Date: Fri, 29 Aug 2025 10:05:32 -0700 Subject: [PATCH 02/11] spot bugs --- .../common/internal/providers/oauth2/DUNAActivity.kt | 2 +- .../providers/oauth2/WebViewAuthorizationFragment.java | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt index 96c47fcc32..ef036448c7 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt @@ -46,7 +46,7 @@ class DUNAActivity: FragmentActivity() { intent?.dataString?.let { intentData -> val result = SwitchBrowserProtocolCoordinator.getIntentToResumeWebViewAuth(applicationContext, intentData) Logger.info(methodTag, intentData) - WebViewAuthorizationFragment.dunaIntent = result + WebViewAuthorizationFragment.setDunaIntent(result) } finishAndRemoveTask() } diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java index 25df968e1f..fc7aea6731 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java @@ -33,7 +33,6 @@ import static com.microsoft.identity.common.java.AuthenticationConstants.SdkPlatformFields.VERSION; import android.annotation.SuppressLint; -import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; @@ -80,7 +79,6 @@ import java.net.URLEncoder; import java.util.Arrays; import java.util.HashMap; -import java.util.Objects; import static com.microsoft.identity.common.java.AuthenticationConstants.OAuth2.UTID; @@ -130,7 +128,7 @@ public class WebViewAuthorizationFragment extends AuthorizationFragment { private boolean isBrokerRequest = false; - public static Intent dunaIntent = null; + private static Intent dunaIntent = null; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -479,4 +477,9 @@ private SwitchBrowserProtocolCoordinator getSwitchBrowserCoordinator() { } return mSwitchBrowserProtocolCoordinator; } + + public static void setDunaIntent(Intent intent) { + dunaIntent = intent; + } + } From efeb00f7af19a2e6a9b6944d6a730ea9d671b5e6 Mon Sep 17 00:00:00 2001 From: p3dr0rv Date: Fri, 29 Aug 2025 10:25:13 -0700 Subject: [PATCH 03/11] Refactor dunaIntent handling to improve thread safety and clarity --- .../oauth2/WebViewAuthorizationFragment.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java index fc7aea6731..9dd1fd52c9 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java @@ -128,7 +128,7 @@ public class WebViewAuthorizationFragment extends AuthorizationFragment { private boolean isBrokerRequest = false; - private static Intent dunaIntent = null; + private static Intent dunaIntent; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -157,7 +157,6 @@ public void onResume() { if (getSwitchBrowserCoordinator().isExpectingSwitchBrowserResume()) { resumeSwitchBrowser(getExtras()); - dunaIntent = new Intent(); } } @@ -168,12 +167,14 @@ public void onResume() { */ @NonNull private Bundle getExtras() { - if (dunaIntent == null) { - return Bundle.EMPTY; + synchronized (WebViewAuthorizationFragment.class) { + if (dunaIntent == null) { + return Bundle.EMPTY; + } + final Bundle extras = dunaIntent.getExtras(); + clearDunaIntent(); + return extras == null ? Bundle.EMPTY : extras; } - final Bundle extras = dunaIntent.getExtras(); - dunaIntent = null; - return extras == null ? Bundle.EMPTY : extras; } /** @@ -478,8 +479,12 @@ private SwitchBrowserProtocolCoordinator getSwitchBrowserCoordinator() { return mSwitchBrowserProtocolCoordinator; } - public static void setDunaIntent(Intent intent) { + public static void setDunaIntent(final Intent intent) { dunaIntent = intent; } + public static void clearDunaIntent() { + dunaIntent = null; + } + } From 38e561a9d706543a9e6514ed28b77f9466f826c7 Mon Sep 17 00:00:00 2001 From: p3dr0rv Date: Fri, 29 Aug 2025 14:12:42 -0700 Subject: [PATCH 04/11] Refactor DUNAActivity and SwitchBrowserProtocolCoordinator to streamline intent handling and improve code clarity --- .../internal/providers/oauth2/DUNAActivity.kt | 14 ++------------ .../SwitchBrowserProtocolCoordinator.kt | 15 ++++----------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt index ef036448c7..3bad88ad41 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt @@ -3,7 +3,6 @@ package com.microsoft.identity.common.internal.providers.oauth2 import android.content.Intent import android.os.Bundle import androidx.fragment.app.FragmentActivity -import com.microsoft.identity.common.internal.ui.webview.switchbrowser.SwitchBrowserProtocolCoordinator import com.microsoft.identity.common.logging.Logger class DUNAActivity: FragmentActivity() { @@ -39,18 +38,9 @@ class DUNAActivity: FragmentActivity() { override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - //setIntent(intent) val methodTag = "$TAG:onNewIntent" - Logger.info(methodTag, "onNewIntent") - - intent?.dataString?.let { intentData -> - val result = SwitchBrowserProtocolCoordinator.getIntentToResumeWebViewAuth(applicationContext, intentData) - Logger.info(methodTag, intentData) - WebViewAuthorizationFragment.setDunaIntent(result) - } + Logger.info(methodTag, "onNewIntent: ${intent.toString()}") + WebViewAuthorizationFragment.setDunaIntent(intent) finishAndRemoveTask() } - - - } \ No newline at end of file diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt index c358663b28..51ac8e8588 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt @@ -27,9 +27,8 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle -import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.AUTHORIZATION_AGENT import com.microsoft.identity.common.adal.internal.AuthenticationConstants.SWITCH_BROWSER -import com.microsoft.identity.common.internal.providers.oauth2.BrokerAuthorizationActivity +import com.microsoft.identity.common.internal.providers.oauth2.DUNAActivity import com.microsoft.identity.common.internal.ui.webview.challengehandlers.SwitchBrowserRequestHandler import com.microsoft.identity.common.java.AuthenticationConstants.AAD.AUTHORIZATION import com.microsoft.identity.common.java.exception.ClientException @@ -37,11 +36,11 @@ import com.microsoft.identity.common.java.opentelemetry.AttributeName import com.microsoft.identity.common.java.opentelemetry.OTelUtility import com.microsoft.identity.common.java.opentelemetry.SpanExtension import com.microsoft.identity.common.java.opentelemetry.SpanName -import com.microsoft.identity.common.java.ui.AuthorizationAgent import com.microsoft.identity.common.logging.Logger import io.opentelemetry.api.trace.Span import io.opentelemetry.api.trace.SpanContext import io.opentelemetry.api.trace.StatusCode +import androidx.core.net.toUri /** * SwitchBrowserProtocolCoordinator is responsible for coordinating the switch browser protocol. @@ -77,15 +76,9 @@ class SwitchBrowserProtocolCoordinator( * Returns the [Intent] used to start the WebView. */ fun getIntentToResumeWebViewAuth(context: Context, intentDataString: String): Intent { - val uri = Uri.parse(intentDataString) - val intent = Intent(context, BrokerAuthorizationActivity::class.java) - intent.putExtra(AUTHORIZATION_AGENT, AuthorizationAgent.WEBVIEW) - // Ensures that if the activity is already running at the top of the stack (WebView), - // a new instance is not created, but its existing instance is brought to the foreground - // instead of launching a new one. - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + val uri = intentDataString.toUri() + val intent = Intent(context, DUNAActivity::class.java) - //intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) intent.putExtra( SWITCH_BROWSER.ACTION_URI, uri.getQueryParameter(SWITCH_BROWSER.ACTION_URI) From 88ec63e02b534a32603dc31fbea1c3f8431013fc Mon Sep 17 00:00:00 2001 From: p3dr0rv Date: Sat, 30 Aug 2025 18:13:13 -0700 Subject: [PATCH 05/11] Refactor DUNA and SwitchBrowser handling: remove DUNAActivity, introduce SwitchBrowserActivity for improved browser switching flow --- .../oauth2/AuthorizationActivityFactory.kt | 4 - .../oauth2/BrokerAuthorizationActivity.java | 15 -- .../internal/providers/oauth2/DUNAActivity.kt | 46 ----- .../providers/oauth2/SwitchBrowserActivity.kt | 157 ++++++++++++++++++ .../oauth2/WebViewAuthorizationFragment.java | 46 ++--- .../SwitchBrowserRequestHandler.kt | 62 ++----- .../SwitchBrowserProtocolCoordinator.kt | 8 +- 7 files changed, 189 insertions(+), 149 deletions(-) delete mode 100644 common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt create mode 100644 common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt index 5424a3c98e..f17fed48d9 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt @@ -65,10 +65,6 @@ object AuthorizationActivityFactory { val libraryConfig = LibraryConfiguration.getInstance() if (ProcessUtil.isBrokerProcess(parameters.context)) { intent = Intent(parameters.context, BrokerAuthorizationActivity::class.java) - if (parameters.requestUrl.contains(AuthenticationConstants.SWITCH_BROWSER.CLIENT_SUPPORTS_FLOW)) { - // In the case of a SwitchBrowser protocol, we need to transition from the browser to the WebView. - // These flags ensure that we have a new task stack that allows for this transition. - } } else if (libraryConfig.isAuthorizationInCurrentTask && parameters.authorizationAgent != AuthorizationAgent.WEBVIEW) { // We exclude the case when the authorization agent is already selected as WEBVIEW because of confusion // that results from attempting to use the CurrentTaskAuthorizationActivity in that case, because as webview diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/BrokerAuthorizationActivity.java b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/BrokerAuthorizationActivity.java index f3df344522..665baf229f 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/BrokerAuthorizationActivity.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/BrokerAuthorizationActivity.java @@ -29,19 +29,4 @@ * in AndroidManifest without overriding MSAL's (In case where MSAL and broker is shipped together). */ public class BrokerAuthorizationActivity extends AuthorizationActivity { - - /** - * Refreshes the WebView with new intent data after the user completes authentication in the browser. - * - *

In the Switch browser flow, once the user finishes authentication in the browser, ETS will send a request - * to the broker containing a code and an action URI. The broker will then send this request data back to the - * WebView authorization activity via an intent. This method is used to refresh the WebView with the new intent - * data that includes the code and action URI. - * see {@link WebViewAuthorizationFragment#onResume()} - */ - @Override - protected void onNewIntent(final Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - } } diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt deleted file mode 100644 index 3bad88ad41..0000000000 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/DUNAActivity.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.microsoft.identity.common.internal.providers.oauth2 - -import android.content.Intent -import android.os.Bundle -import androidx.fragment.app.FragmentActivity -import com.microsoft.identity.common.logging.Logger - -class DUNAActivity: FragmentActivity() { - - companion object { - private val TAG: String = DUNAActivity::class.java.simpleName - } - - - override fun onCreate(savedInstanceState: Bundle?) { - val methodTag = "$TAG:onCreate" - super.onCreate(savedInstanceState) - - - Logger.info(methodTag, "DUNAActivity onCreate") - if (savedInstanceState == null) { - Logger.info(methodTag, "Extracting browser intent from extras for DUNA flow") - if (intent == null) { - Logger.warn(methodTag, "Intent is null") - finish() - return - } - val browser = intent.getParcelableExtra("browser_intent") - if (browser == null) { - Logger.warn(methodTag, "No browser intent found in extras") - } - Logger.info(methodTag, "Launching Custom Chrome Tab intent for DUNA authentication") - startActivity(browser) - } else { - Logger.info(methodTag, "Activity restored from saved state - Skipping browser intent launch") - } - } - - override fun onNewIntent(intent: Intent?) { - super.onNewIntent(intent) - val methodTag = "$TAG:onNewIntent" - Logger.info(methodTag, "onNewIntent: ${intent.toString()}") - WebViewAuthorizationFragment.setDunaIntent(intent) - finishAndRemoveTask() - } -} \ No newline at end of file diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt new file mode 100644 index 0000000000..265334420e --- /dev/null +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package com.microsoft.identity.common.internal.providers.oauth2 + +import android.content.Intent +import android.os.Bundle +import androidx.browser.customtabs.CustomTabsIntent +import androidx.fragment.app.FragmentActivity +import com.microsoft.identity.common.logging.Logger +import androidx.core.net.toUri + + +/** + * Activity responsible for handling browser switching flows. + * + * This activity serves as an intermediary between the WebView-based authentication and external browser + * authentication. When a Switch Browser challenge is received in [WebViewAuthorizationFragment], this activity + * is launched to handle the browser switch operation. + * + * **Flow Overview:** + * 1. WebViewAuthorizationFragment receives a SwitchBrowser challenge + * 2. This activity is launched with browser configuration parameters + * 3. Activity launches the specified browser (Custom Tabs or standard browser) + * 4. User completes authentication in the external browser + * + * 5. Browser redirects back to this activity via onNewIntent() + * 6. Activity passes the result back to WebViewAuthorizationFragment + * 7. Activity finishes and removes itself from the task stack + * + * **Security Note:** This activity is not exported and can only be launched within the app + * to prevent external apps from triggering unwanted browser switches. + * + * @see WebViewAuthorizationFragment + */ +class SwitchBrowserActivity: FragmentActivity() { + + companion object { + private val TAG: String = SwitchBrowserActivity::class.java.simpleName + + /** Intent extra key for the target browser package name */ + const val BROWSER_PACKAGE_NAME = "browser_package_name" + + /** Intent extra key indicating if the browser supports Custom Tabs */ + const val BROWSER_SUPPORTS_CUSTOM_TABS = "browser_supports_custom_tabs" + + /** Intent extra key for the URI to process in the browser */ + const val PROCESS_URI = "process_uri" + } + + /** + * Initializes the activity and launches the appropriate browser for DUNA authentication. + * + * This method extracts the browser configuration from intent extras and launches either + * a Custom Tabs intent or a standard browser intent based on browser capabilities. + * + * @param savedInstanceState Saved instance state bundle (unused in this implementation) + */ + override fun onCreate(savedInstanceState: Bundle?) { + val methodTag = "$TAG:onCreate" + super.onCreate(savedInstanceState) + Logger.info(methodTag, "SwitchBrowserActivity onCreate - Starting browser switch flow") + + // Extract configuration parameters from intent extras + val extras = this.intent.extras ?: Bundle() + val browserPackageName = extras.getString(BROWSER_PACKAGE_NAME) + val browserSupportsCustomTabs = extras.getBoolean(BROWSER_SUPPORTS_CUSTOM_TABS, false) + val processUri = extras.getString(PROCESS_URI) + + // Validate required parameters + if (browserPackageName.isNullOrBlank()) { + Logger.error(methodTag, "No browser package name found in extras - Cannot proceed with browser switch", null) + finish() + return + } + if (processUri.isNullOrBlank()) { + Logger.error(methodTag, "No process URI found in extras - Cannot proceed with browser switch", null) + finish() + return + } + + Logger.info( + methodTag, + "Launching switch browser request on browser: $browserPackageName, Custom Tabs supported: $browserSupportsCustomTabs" + ) + + // Launch browser based on Custom Tabs support + if (browserSupportsCustomTabs) { + // Use Custom Tabs for better user experience and security + Logger.info(methodTag, "Launching Custom Tabs intent for DUNA authentication") + val customTabsIntent = CustomTabsIntent.Builder().build().apply { + intent.setPackage(browserPackageName) + } + customTabsIntent.launchUrl(this, processUri.toUri()) + } else { + // Fallback to standard browser intent + Logger.info(methodTag, "Launching standard browser intent for DUNA authentication") + val browserIntent = Intent(Intent.ACTION_VIEW).apply { + data = processUri.toUri() + setPackage(browserPackageName) + } + startActivity(browserIntent) + } + } + + /** + * Handles the redirect back from the browser after DUNA authentication completion. + * + * This method is called when the browser redirects back to the app with the authentication + * result. The intent contains the authentication response which is passed back to the + * WebViewAuthorizationFragment for processing. + * + * **Important:** This method also finishes the activity and removes it from the task stack + * to prevent it from remaining in the back stack after the authentication flow completes. + * + * @param intent The intent containing the authentication result from the browser redirect + */ + override fun onNewIntent(intent: Intent?) { + val methodTag = "$TAG:onNewIntent" + super.onNewIntent(intent) + Logger.info(methodTag, "Received redirect from browser - Processing DUNA authentication result") + + // Update the activity's intent with the new intent containing the auth result + setIntent(intent) + + // Pass the authentication result back to WebViewAuthorizationFragment + Logger.info(methodTag, "Passing authentication result back to WebViewAuthorizationFragment") + if (intent != null) { + WebViewAuthorizationFragment.setSwitchBrowserBundle(intent.extras) + } else { + Logger.error(methodTag, "Received null intent in onNewIntent - Cannot pass result back", null) + } + + // Clean up: finish this activity and remove it from task stack + Logger.info(methodTag, "Browser switch complete - Finishing activity and removing from task stack") + finishAndRemoveTask() + } +} diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java index 9dd1fd52c9..dfa964d02a 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java @@ -128,7 +128,7 @@ public class WebViewAuthorizationFragment extends AuthorizationFragment { private boolean isBrokerRequest = false; - private static Intent dunaIntent; + private static Bundle switchBrowserBundle; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -154,46 +154,35 @@ public void onCreate(@Nullable Bundle savedInstanceState) { public void onResume() { super.onResume(); Logger.info(TAG + ":onResume", "WebViewAuthorizationFragment onResume"); - if (getSwitchBrowserCoordinator().isExpectingSwitchBrowserResume()) { - resumeSwitchBrowser(getExtras()); - } - } - - /** - * Get the extras from the activity intent. - * - * @return Bundle with the extras - */ - @NonNull - private Bundle getExtras() { - synchronized (WebViewAuthorizationFragment.class) { - if (dunaIntent == null) { - return Bundle.EMPTY; - } - final Bundle extras = dunaIntent.getExtras(); - clearDunaIntent(); - return extras == null ? Bundle.EMPTY : extras; + resumeSwitchBrowser(); + } else { + setSwitchBrowserBundle(null); } } /** * Resume the switch browser protocol flow. - * - * @param extras Bundle with the data to resume the switch browser protocol flow. */ - private void resumeSwitchBrowser(@NonNull final Bundle extras) { + private void resumeSwitchBrowser() { final String methodTag = TAG + ":resumeSwitchBrowser"; try { + if (switchBrowserBundle == null) { + throw new ClientException( + ClientException.NULL_OBJECT, + "No switch browser bundle found to resume the flow." + ); + } Logger.info(methodTag, "Resuming switch browser flow"); getSwitchBrowserCoordinator().processSwitchBrowserResume( mAuthorizationRequestUrl, - extras, + switchBrowserBundle, (switchBrowserResumeUri, switchBrowserResumeHeaders) -> { launchWebView(switchBrowserResumeUri.toString(), switchBrowserResumeHeaders); return null; } ); + setSwitchBrowserBundle(null); } catch (final ClientException e) { Logger.error(methodTag, "Error processing switch browser resume", e); sendResult(RawAuthorizationResult.fromException(e)); @@ -479,12 +468,7 @@ private SwitchBrowserProtocolCoordinator getSwitchBrowserCoordinator() { return mSwitchBrowserProtocolCoordinator; } - public static void setDunaIntent(final Intent intent) { - dunaIntent = intent; + public static void setSwitchBrowserBundle(@Nullable final Bundle bundle) { + switchBrowserBundle = bundle; } - - public static void clearDunaIntent() { - dunaIntent = null; - } - } diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandler.kt b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandler.kt index 263f7fc349..2f191a7c16 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandler.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandler.kt @@ -23,12 +23,10 @@ package com.microsoft.identity.common.internal.ui.webview.challengehandlers import android.app.Activity -import android.content.Context import android.content.Intent import com.microsoft.identity.common.adal.internal.AuthenticationConstants.SWITCH_BROWSER -import com.microsoft.identity.common.internal.providers.oauth2.DUNAActivity +import com.microsoft.identity.common.internal.providers.oauth2.SwitchBrowserActivity import com.microsoft.identity.common.internal.ui.browser.AndroidBrowserSelector -import com.microsoft.identity.common.internal.ui.browser.CustomTabsManager import com.microsoft.identity.common.internal.ui.webview.switchbrowser.SwitchBrowserUriHelper import com.microsoft.identity.common.internal.ui.webview.switchbrowser.SwitchBrowserUriHelper.isSwitchBrowserRedirectUrl import com.microsoft.identity.common.java.browser.IBrowserSelector @@ -49,8 +47,6 @@ import io.opentelemetry.api.trace.StatusCode */ class SwitchBrowserRequestHandler( private val activity: Activity, - private val context: Context, - private val customTabsManager: CustomTabsManager, private val browserSelector: IBrowserSelector, private val spanContext: SpanContext? ) : IChallengeHandler { @@ -59,8 +55,7 @@ class SwitchBrowserRequestHandler( OTelUtility.createSpanFromParent(SpanName.SwitchBrowserProcess.name, spanContext) } - var isChallengeHandled: Boolean = false - private set + var switchBrowserChallengeActive: Boolean = false companion object { private val TAG = SwitchBrowserRequestHandler::class.simpleName @@ -68,8 +63,6 @@ class SwitchBrowserRequestHandler( constructor(activity: Activity, spanContext: SpanContext?) : this( activity, - activity.applicationContext, - CustomTabsManager(activity.applicationContext), AndroidBrowserSelector(activity.applicationContext), spanContext ) @@ -109,38 +102,6 @@ class SwitchBrowserRequestHandler( span.end() throw exception } - - // Create an intent to launch the browser - val browserIntent: Intent - if (browser.isCustomTabsServiceSupported) { - Logger.info(methodTag, "CustomTabsService is supported.") - //create customTabsIntent - if (!customTabsManager.bind(context, browser.packageName)) { - Logger.warn(methodTag, "Failed to bind CustomTabsService.") - browserIntent = Intent(Intent.ACTION_VIEW) - } else { - browserIntent = customTabsManager.customTabsIntent.intent - } - } else { - Logger.warn(methodTag, "CustomTabsService is NOT supported") - browserIntent = Intent(Intent.ACTION_VIEW) - } - Logger.info( - methodTag, - "Launching switch browser request on browser: ${browser.packageName}" - ) - browserIntent.setPackage(browser.packageName) - browserIntent.setData(switchBrowserChallenge.processUri) - val dunaIntent = Intent(activity, DUNAActivity::class.java) - dunaIntent.putExtra("browser_intent", browserIntent) - dunaIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - activity.startActivity(dunaIntent) - //activity.startActivity(browserIntent) - isChallengeHandled = true - span.setAttribute( - AttributeName.is_switch_browser_request_handled.name, - isChallengeHandled - ) span.setAttribute( AttributeName.browser_package_name.name, browser.packageName @@ -149,7 +110,15 @@ class SwitchBrowserRequestHandler( AttributeName.is_custom_tabs_supported.name, browser.isCustomTabsServiceSupported ) + val switchBrowserIntent = Intent(activity, SwitchBrowserActivity::class.java).apply { + putExtra(SwitchBrowserActivity.BROWSER_PACKAGE_NAME, browser.packageName) + putExtra(SwitchBrowserActivity.BROWSER_SUPPORTS_CUSTOM_TABS, browser.isCustomTabsServiceSupported) + putExtra(SwitchBrowserActivity.PROCESS_URI, switchBrowserChallenge.processUri.toString()) + setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + activity.startActivity(switchBrowserIntent) span.setStatus(StatusCode.OK) + switchBrowserChallengeActive = true span.end() } } @@ -170,15 +139,10 @@ class SwitchBrowserRequestHandler( } /** - * Handles the necessary cleanup after a challenge is completed. - * This includes unbinding the custom tabs manager and resetting the challenge handled flag. + * Reset the challenge state. + * This method is called after processing the switch browser resume action. */ fun resetChallengeState() { - // Unbind the custom tabs manager - customTabsManager.unbind() - // Reset the challenge handled flag - isChallengeHandled = false - + switchBrowserChallengeActive = false } - } diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt index 51ac8e8588..7bbc53fcd2 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt @@ -28,7 +28,7 @@ import android.content.Intent import android.net.Uri import android.os.Bundle import com.microsoft.identity.common.adal.internal.AuthenticationConstants.SWITCH_BROWSER -import com.microsoft.identity.common.internal.providers.oauth2.DUNAActivity +import com.microsoft.identity.common.internal.providers.oauth2.SwitchBrowserActivity import com.microsoft.identity.common.internal.ui.webview.challengehandlers.SwitchBrowserRequestHandler import com.microsoft.identity.common.java.AuthenticationConstants.AAD.AUTHORIZATION import com.microsoft.identity.common.java.exception.ClientException @@ -77,7 +77,7 @@ class SwitchBrowserProtocolCoordinator( */ fun getIntentToResumeWebViewAuth(context: Context, intentDataString: String): Intent { val uri = intentDataString.toUri() - val intent = Intent(context, DUNAActivity::class.java) + val intent = Intent(context, SwitchBrowserActivity::class.java) intent.putExtra( SWITCH_BROWSER.ACTION_URI, @@ -149,7 +149,7 @@ class SwitchBrowserProtocolCoordinator( */ fun isExpectingSwitchBrowserResume(): Boolean { val methodTag = "$TAG:isExpectingSwitchBrowserResume" - Logger.verbose(methodTag, "ExpectingRequest: ${switchBrowserRequestHandler.isChallengeHandled}") - return switchBrowserRequestHandler.isChallengeHandled + Logger.verbose(methodTag, "ExpectingRequest: ${switchBrowserRequestHandler.switchBrowserChallengeActive}") + return switchBrowserRequestHandler.switchBrowserChallengeActive } } From 68677715e61dfabf59cd4d9d0fe097477c4d5e52 Mon Sep 17 00:00:00 2001 From: p3dr0rv Date: Sat, 30 Aug 2025 18:39:23 -0700 Subject: [PATCH 06/11] Refactor SwitchBrowserActivity and WebViewAuthorizationFragment for improved logging and clarity; streamline intent creation in SwitchBrowserProtocolCoordinator --- .../oauth2/BrokerAuthorizationActivity.java | 2 -- .../providers/oauth2/SwitchBrowserActivity.kt | 11 +++++-- .../oauth2/WebViewAuthorizationFragment.java | 8 +++-- .../SwitchBrowserProtocolCoordinator.kt | 29 +++++++++---------- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/BrokerAuthorizationActivity.java b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/BrokerAuthorizationActivity.java index 665baf229f..bac2575359 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/BrokerAuthorizationActivity.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/BrokerAuthorizationActivity.java @@ -22,8 +22,6 @@ // THE SOFTWARE. package com.microsoft.identity.common.internal.providers.oauth2; -import android.content.Intent; - /** * Declares as a separate class so that we can specify attributes exclusively to :auth process * in AndroidManifest without overriding MSAL's (In case where MSAL and broker is shipped together). diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt index 265334420e..57f50c24fa 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt @@ -42,11 +42,18 @@ import androidx.core.net.toUri * 2. This activity is launched with browser configuration parameters * 3. Activity launches the specified browser (Custom Tabs or standard browser) * 4. User completes authentication in the external browser - * - * 5. Browser redirects back to this activity via onNewIntent() + * 5. BrokerBrowserRedirectActivity is launched when the redirect URI is triggered. + * 5. BrokerBrowserRedirectActivity redirects back to this activity via onNewIntent() * 6. Activity passes the result back to WebViewAuthorizationFragment * 7. Activity finishes and removes itself from the task stack * + * Activity back stack behavior: + * 1 BrokerAuthorizationActivity hosting WebViewAuthorizationFragment --launches--> SwitchBrowserActivity in a new task. + * 2 SwitchBrowserActivity --launches--> 3rd Party Browser (Custom Tabs or standard browser) in current task. + * 3 3rd Party Browser --redirects to--> BrokerBrowserRedirectActivity in a new task. + * 4 BrokerBrowserRedirectActivity -- launches--> SwitchBrowserActivity in the existing task, and finishes current task. + * 5 SwitchBrowserActivity --passes result to--> WebViewAuthorizationFragment, and finishes current activity stack. + * * **Security Note:** This activity is not exported and can only be launched within the app * to prevent external apps from triggering unwanted browser switches. * diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java index dfa964d02a..75a1d4efce 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java @@ -134,7 +134,7 @@ public class WebViewAuthorizationFragment extends AuthorizationFragment { public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); final String methodTag = TAG + ":onCreate"; - Logger.info(methodTag, "WebViewAuthorizationFragment onCreate"); + Logger.verbose(methodTag, "WebViewAuthorizationFragment onCreate"); final FragmentActivity activity = getActivity(); if (activity != null) { WebViewUtil.setDataDirectorySuffix(activity.getApplicationContext()); @@ -153,7 +153,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { @Override public void onResume() { super.onResume(); - Logger.info(TAG + ":onResume", "WebViewAuthorizationFragment onResume"); + Logger.verbose(TAG + ":onResume", "WebViewAuthorizationFragment onResume"); if (getSwitchBrowserCoordinator().isExpectingSwitchBrowserResume()) { resumeSwitchBrowser(); } else { @@ -468,6 +468,10 @@ private SwitchBrowserProtocolCoordinator getSwitchBrowserCoordinator() { return mSwitchBrowserProtocolCoordinator; } + /** + * Set the switch browser bundle to be used when resuming the flow. + * @param bundle The bundle containing the data needed to resume the flow. + */ public static void setSwitchBrowserBundle(@Nullable final Bundle bundle) { switchBrowserBundle = bundle; } diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt index 7bbc53fcd2..c8a04bad57 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt @@ -77,21 +77,20 @@ class SwitchBrowserProtocolCoordinator( */ fun getIntentToResumeWebViewAuth(context: Context, intentDataString: String): Intent { val uri = intentDataString.toUri() - val intent = Intent(context, SwitchBrowserActivity::class.java) - - intent.putExtra( - SWITCH_BROWSER.ACTION_URI, - uri.getQueryParameter(SWITCH_BROWSER.ACTION_URI) - ) - intent.putExtra( - SWITCH_BROWSER.CODE, - uri.getQueryParameter(SWITCH_BROWSER.CODE) - ) - intent.putExtra( - SWITCH_BROWSER.STATE, - uri.getQueryParameter(SWITCH_BROWSER.STATE) - ) - return intent + return Intent(context, SwitchBrowserActivity::class.java).apply { + putExtra( + SWITCH_BROWSER.ACTION_URI, + uri.getQueryParameter(SWITCH_BROWSER.ACTION_URI) + ) + putExtra( + SWITCH_BROWSER.CODE, + uri.getQueryParameter(SWITCH_BROWSER.CODE) + ) + putExtra( + SWITCH_BROWSER.STATE, + uri.getQueryParameter(SWITCH_BROWSER.STATE) + ) + } } } From 82c27d246e84800e527f8b0d44c8bf86a350b224 Mon Sep 17 00:00:00 2001 From: p3dr0rv Date: Sat, 30 Aug 2025 19:49:03 -0700 Subject: [PATCH 07/11] Refactor SwitchBrowserActivity and SwitchBrowserRequestHandlerTest to remove unused parameters for improved clarity and simplicity --- .../providers/oauth2/SwitchBrowserActivity.kt | 3 --- .../SwitchBrowserRequestHandler.kt | 6 +++--- .../SwitchBrowserProtocolCoordinator.kt | 4 ++-- .../SwitchBrowserRequestHandlerTest.kt | 18 ++++-------------- .../SwitchBrowserProtocolCoordinatorTest.kt | 19 ++++++++++--------- 5 files changed, 19 insertions(+), 31 deletions(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt index 57f50c24fa..561cd8b2d0 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt @@ -85,7 +85,6 @@ class SwitchBrowserActivity: FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { val methodTag = "$TAG:onCreate" super.onCreate(savedInstanceState) - Logger.info(methodTag, "SwitchBrowserActivity onCreate - Starting browser switch flow") // Extract configuration parameters from intent extras val extras = this.intent.extras ?: Bundle() @@ -144,8 +143,6 @@ class SwitchBrowserActivity: FragmentActivity() { override fun onNewIntent(intent: Intent?) { val methodTag = "$TAG:onNewIntent" super.onNewIntent(intent) - Logger.info(methodTag, "Received redirect from browser - Processing DUNA authentication result") - // Update the activity's intent with the new intent containing the auth result setIntent(intent) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandler.kt b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandler.kt index 2f191a7c16..401fbb13bc 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandler.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandler.kt @@ -55,7 +55,7 @@ class SwitchBrowserRequestHandler( OTelUtility.createSpanFromParent(SpanName.SwitchBrowserProcess.name, spanContext) } - var switchBrowserChallengeActive: Boolean = false + var isSwitchBrowserChallengeActive: Boolean = false companion object { private val TAG = SwitchBrowserRequestHandler::class.simpleName @@ -118,7 +118,7 @@ class SwitchBrowserRequestHandler( } activity.startActivity(switchBrowserIntent) span.setStatus(StatusCode.OK) - switchBrowserChallengeActive = true + isSwitchBrowserChallengeActive = true span.end() } } @@ -143,6 +143,6 @@ class SwitchBrowserRequestHandler( * This method is called after processing the switch browser resume action. */ fun resetChallengeState() { - switchBrowserChallengeActive = false + isSwitchBrowserChallengeActive = false } } diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt index c8a04bad57..94dc96c8af 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt @@ -148,7 +148,7 @@ class SwitchBrowserProtocolCoordinator( */ fun isExpectingSwitchBrowserResume(): Boolean { val methodTag = "$TAG:isExpectingSwitchBrowserResume" - Logger.verbose(methodTag, "ExpectingRequest: ${switchBrowserRequestHandler.switchBrowserChallengeActive}") - return switchBrowserRequestHandler.switchBrowserChallengeActive + Logger.verbose(methodTag, "ExpectingRequest: ${switchBrowserRequestHandler.isSwitchBrowserChallengeActive}") + return switchBrowserRequestHandler.isSwitchBrowserChallengeActive } } diff --git a/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandlerTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandlerTest.kt index 1a17bcb844..65da22c900 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandlerTest.kt +++ b/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandlerTest.kt @@ -23,10 +23,8 @@ package com.microsoft.identity.common.internal.ui.webview.challengehandlers import android.app.Activity -import android.content.Context import android.content.Intent import android.net.Uri -import com.microsoft.identity.common.internal.ui.browser.CustomTabsManager import com.microsoft.identity.common.internal.ui.webview.switchbrowser.SwitchBrowserUriHelper import com.microsoft.identity.common.java.browser.Browser import com.microsoft.identity.common.java.browser.IBrowserSelector @@ -58,14 +56,12 @@ class SwitchBrowserRequestHandlerTest { activityExecuted = true null }.whenever(mockActivity).startActivity(any()) - val context = mock(Context::class.java) - val customTabsManager = mock(CustomTabsManager::class.java) val challenge = mock(SwitchBrowserChallenge::class.java) `when`(challenge.processUri).thenReturn(Uri.parse("https://login.microsoft.com?state=123")) `when`(challenge.authorizationUrl).thenReturn("https://auth.com?state=123") val browserSelector = // Browser available IBrowserSelector { _, _ -> Browser("fakeBrowser", emptySet(), "browser", false) } - val handler = SwitchBrowserRequestHandler(mockActivity, context, customTabsManager, browserSelector, null) + val handler = SwitchBrowserRequestHandler(mockActivity, browserSelector, null) handler.processChallenge(challenge) Assert.assertTrue(activityExecuted) } @@ -80,14 +76,12 @@ class SwitchBrowserRequestHandlerTest { activityExecuted = true null }.whenever(mockActivity).startActivity(any()) - val context = mock(Context::class.java) - val customTabsManager = mock(CustomTabsManager::class.java) val challenge = mock(SwitchBrowserChallenge::class.java) `when`(challenge.processUri).thenReturn(Uri.parse("https://login.microsoft.com")) `when`(challenge.authorizationUrl).thenReturn("https://auth.com") val browserSelector = // Browser available IBrowserSelector { _, _ -> Browser("fakeBrowser", emptySet(), "browser", false) } - val handler = SwitchBrowserRequestHandler(mockActivity, context, customTabsManager, browserSelector, null) + val handler = SwitchBrowserRequestHandler(mockActivity, browserSelector, null) handler.processChallenge(challenge) Assert.assertTrue(activityExecuted) } @@ -97,13 +91,11 @@ class SwitchBrowserRequestHandlerTest { // Mock parameters val activity = mock(Activity::class.java) doNothing().`when`(activity).startActivity(Intent()) - val context = mock(Context::class.java) - val customTabsManager = mock(CustomTabsManager::class.java) val challenge = mock(SwitchBrowserChallenge::class.java) `when`(challenge.processUri).thenReturn(Uri.parse("https://login.microsoft.com?state=123")) `when`(challenge.authorizationUrl).thenReturn("https://auth.com?state=123") val browserSelector = IBrowserSelector { _, _ -> null } // No browser available - val handler = SwitchBrowserRequestHandler(activity, context, customTabsManager, browserSelector, null) + val handler = SwitchBrowserRequestHandler(activity, browserSelector, null) val exception = Assert.assertThrows(ClientException::class.java) { handler.processChallenge(challenge) } @@ -116,14 +108,12 @@ class SwitchBrowserRequestHandlerTest { isStateRequired(true) // Mock parameters val mockActivity = mock() - val context = mock(Context::class.java) - val customTabsManager = mock(CustomTabsManager::class.java) val challenge = mock(SwitchBrowserChallenge::class.java) `when`(challenge.processUri).thenReturn(Uri.parse("https://login.microsoft.com?state=123")) `when`(challenge.authorizationUrl).thenReturn("https://auth.com?state=456") val browserSelector = // Browser available IBrowserSelector { _, _ -> Browser("fakeBrowser", emptySet(), "browser", false) } - val handler = SwitchBrowserRequestHandler(mockActivity, context, customTabsManager, browserSelector, null) + val handler = SwitchBrowserRequestHandler(mockActivity, browserSelector, null) val exception = Assert.assertThrows(ClientException::class.java) { handler.processChallenge(challenge) } diff --git a/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinatorTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinatorTest.kt index d0bee03e11..c319d48bed 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinatorTest.kt +++ b/common/src/test/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinatorTest.kt @@ -152,7 +152,7 @@ class SwitchBrowserProtocolCoordinatorTest { fun `test isExpectingSwitchBrowserResume with handler true`() { // Mock parameters val mockSwitchBrowserRequestHandler = mock(SwitchBrowserRequestHandler::class.java) - `when`(mockSwitchBrowserRequestHandler.isChallengeHandled).then { true } + `when`(mockSwitchBrowserRequestHandler.isSwitchBrowserChallengeActive).then { true } // Create an instance of SwitchBrowserProtocolCoordinator val coordinator = SwitchBrowserProtocolCoordinator(mockSwitchBrowserRequestHandler) @@ -167,7 +167,7 @@ class SwitchBrowserProtocolCoordinatorTest { fun `test isExpectingSwitchBrowserResume with handler false`() { // Mock parameters val mockSwitchBrowserRequestHandler = mock(SwitchBrowserRequestHandler::class.java) - `when`(mockSwitchBrowserRequestHandler.isChallengeHandled).then { false } + `when`(mockSwitchBrowserRequestHandler.isSwitchBrowserChallengeActive).then { false } // Create an instance of SwitchBrowserProtocolCoordinator val coordinator = SwitchBrowserProtocolCoordinator(mockSwitchBrowserRequestHandler) @@ -210,8 +210,12 @@ class SwitchBrowserProtocolCoordinatorTest { val mockContext = mock(Context::class.java) val actionUri = "mock-action-uri" val code = "mock-code" - val intentDataString = "${Broker.NEW_BROKER_REDIRECT_URI}/${SWITCH_BROWSER.RESUME_PATH}?" + - "${SWITCH_BROWSER.ACTION_URI}=$actionUri&${SWITCH_BROWSER.CODE}=$code" + val state = "mock-state" + val intentDataString = + "${Broker.NEW_BROKER_REDIRECT_URI}/${SWITCH_BROWSER.RESUME_PATH}?" + + "${SWITCH_BROWSER.ACTION_URI}=$actionUri&" + + "${SWITCH_BROWSER.CODE}=$code&" + + "${SWITCH_BROWSER.STATE}=$state" // Call the method to be tested val intent = SwitchBrowserProtocolCoordinator @@ -219,15 +223,12 @@ class SwitchBrowserProtocolCoordinatorTest { // Verify the result Assert.assertEquals( - Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP, + 0, intent.flags ) Assert.assertEquals(actionUri, intent.getStringExtra(SWITCH_BROWSER.ACTION_URI)) Assert.assertEquals(code, intent.getStringExtra(SWITCH_BROWSER.CODE)) - Assert.assertEquals( - AuthorizationAgent.WEBVIEW, - intent.getSerializableExtra(AUTHORIZATION_AGENT) as AuthorizationAgent - ) + Assert.assertEquals(state, intent.getStringExtra(SWITCH_BROWSER.STATE)) } @Test From 3cba3719f51c51f642bc1702c27eb83591860145 Mon Sep 17 00:00:00 2001 From: p3dr0rv Date: Sat, 30 Aug 2025 22:53:43 -0700 Subject: [PATCH 08/11] Implement CCT launch state management in SwitchBrowserActivity to handle user cancellations and improve activity lifecycle management --- changelog.txt | 1 + .../providers/oauth2/SwitchBrowserActivity.kt | 92 +++++++++++++++++-- .../SwitchBrowserProtocolCoordinator.kt | 4 + 3 files changed, 90 insertions(+), 7 deletions(-) diff --git a/changelog.txt b/changelog.txt index 6afc424369..214f090d7d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,6 @@ vNext ---------- +- [PATCH] Fix Switch browser back stack (#2750) - [MINOR] Move ests telemetry behind feature flag (#2742) - [MINOR] Update Broker ATS flow for Resource Account (#2704) - [PATCH] Handle BadTokenException gracefully in CBA dialogs (#2731) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt index 561cd8b2d0..916ad09ac2 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt @@ -61,6 +61,9 @@ import androidx.core.net.toUri */ class SwitchBrowserActivity: FragmentActivity() { + // Flag to track if a Custom Chrome Tab (CCT) has been launched + private var cctLaunched = false + companion object { private val TAG: String = SwitchBrowserActivity::class.java.simpleName @@ -72,6 +75,9 @@ class SwitchBrowserActivity: FragmentActivity() { /** Intent extra key for the URI to process in the browser */ const val PROCESS_URI = "process_uri" + + /** Intent extra key indicating a resume request from the browser redirect */ + const val RESUME_REQUEST = "resume_request" } /** @@ -85,7 +91,23 @@ class SwitchBrowserActivity: FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { val methodTag = "$TAG:onCreate" super.onCreate(savedInstanceState) + Logger.info(methodTag, "SwitchBrowserActivity created - Launching browser") + launchBrowser() + } + + /** + * Launches the specified browser for DUNA authentication based on intent extras. + * + * This method reads the target browser package name, Custom Tabs support flag, + * and the process URI from the intent extras. It then constructs and launches + * either a Custom Tabs intent or a standard browser intent accordingly. + * + * If required parameters are missing, it logs an error and finishes the activity. + */ + private fun launchBrowser() { + val methodTag = "$TAG:launchBrowser" + cctLaunched = false // Extract configuration parameters from intent extras val extras = this.intent.extras ?: Bundle() val browserPackageName = extras.getString(BROWSER_PACKAGE_NAME) @@ -144,18 +166,74 @@ class SwitchBrowserActivity: FragmentActivity() { val methodTag = "$TAG:onNewIntent" super.onNewIntent(intent) // Update the activity's intent with the new intent containing the auth result + Logger.info(methodTag, "On new intent received.") setIntent(intent) - // Pass the authentication result back to WebViewAuthorizationFragment - Logger.info(methodTag, "Passing authentication result back to WebViewAuthorizationFragment") if (intent != null) { - WebViewAuthorizationFragment.setSwitchBrowserBundle(intent.extras) - } else { - Logger.error(methodTag, "Received null intent in onNewIntent - Cannot pass result back", null) + if (intent.hasExtra(PROCESS_URI)) { + // Handle scenario where a new browser switch request is received while one is already in progress + // This can occur when the user initiates another auth request before completing the first one. + Logger.warn( + methodTag, + "Received new switch browser request while one is already in progress" + + " - Restarting browser switch flow" + ) + // Launch the new browser request, which will reset cctLaunched and start fresh + launchBrowser() + return + } + if (intent.hasExtra(RESUME_REQUEST)) { + WebViewAuthorizationFragment.setSwitchBrowserBundle(intent.extras) + // Clean up: finish this activity and remove it from task stack + Logger.info(methodTag, "Finishing activity and removing from task stack") + finishAndRemoveTask() + return + } } - // Clean up: finish this activity and remove it from task stack - Logger.info(methodTag, "Browser switch complete - Finishing activity and removing from task stack") + Logger.info(methodTag, "Unexpected intent - Finishing activity and removing from task stack") finishAndRemoveTask() } + + /** + * Handles the activity resume lifecycle event and manages Custom Chrome Tab (CCT) launch state. + * + * This method implements a critical part of the browser switch flow by tracking whether a Custom Chrome Tab + * has been launched and handling the case where the user returns to this activity without completing + * the authentication flow in the browser. + * + * **Behavior Logic:** + * - On first resume (after onCreate): Sets cctLaunched flag to true and continues normally + * - On subsequent resumes: If CCT was already launched, assumes user backed out of browser and finishes activity + * + * **Why This Logic is Needed:** + * When a Custom Chrome Tab is launched, this activity goes into the background. If the user presses the back + * button in the CCT or otherwise returns to this activity without completing authentication, we need to + * clean up and finish this activity to prevent it from remaining in the back stack. + * + * **Flow Scenarios:** + * 1. **Normal Flow**: onCreate → onResume (1st time) → CCT launched → user completes auth → onNewIntent → finish + * 2. **User Cancellation**: onCreate → onResume (1st time) → CCT launched → user backs out → onResume (2nd time) → finish + * + * **Important Notes:** + * - This prevents the activity from staying alive indefinitely if authentication is cancelled + * - Uses finishAndRemoveTask() to clean up the entire task stack, not just this activity + * - The cctLaunched flag is essential for distinguishing between the initial resume and subsequent resumes + */ + override fun onResume() { + super.onResume() + val methodTag = "$TAG:onResume" + Logger.info(methodTag, "onResume called - Managing CCT launch state") + + if (cctLaunched) { + // User has returned to this activity after CCT was launched, likely due to backing out + Logger.info(methodTag, "CCT was launched previously and user returned - Assuming cancellation, finishing activity") + finishAndRemoveTask() + } else { + // First resume after onCreate - mark CCT as launched for future reference + Logger.info(methodTag, "First resume after onCreate - Marking CCT as launched") + } + + cctLaunched = true + } } diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt index 94dc96c8af..bfcfc29d01 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/switchbrowser/SwitchBrowserProtocolCoordinator.kt @@ -90,6 +90,10 @@ class SwitchBrowserProtocolCoordinator( SWITCH_BROWSER.STATE, uri.getQueryParameter(SWITCH_BROWSER.STATE) ) + putExtra( + SwitchBrowserActivity.RESUME_REQUEST, + true + ) } } } From bcfc22e02c5624980542f06ba5d77358ab4937da Mon Sep 17 00:00:00 2001 From: pedro romero vargas <76129899+p3dr0rv@users.noreply.github.com> Date: Mon, 1 Sep 2025 14:24:39 -0700 Subject: [PATCH 09/11] Update common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../common/internal/providers/oauth2/SwitchBrowserActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt index 916ad09ac2..1f02b04a13 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt @@ -59,7 +59,7 @@ import androidx.core.net.toUri * * @see WebViewAuthorizationFragment */ -class SwitchBrowserActivity: FragmentActivity() { +class SwitchBrowserActivity : FragmentActivity() { // Flag to track if a Custom Chrome Tab (CCT) has been launched private var cctLaunched = false From 7fd38161f5b6a7a1fa41744230c0f5e3eda78f0d Mon Sep 17 00:00:00 2001 From: pedro romero vargas <76129899+p3dr0rv@users.noreply.github.com> Date: Mon, 1 Sep 2025 14:24:54 -0700 Subject: [PATCH 10/11] Update common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../internal/providers/oauth2/WebViewAuthorizationFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java index 75a1d4efce..1b047f2187 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/WebViewAuthorizationFragment.java @@ -472,7 +472,7 @@ private SwitchBrowserProtocolCoordinator getSwitchBrowserCoordinator() { * Set the switch browser bundle to be used when resuming the flow. * @param bundle The bundle containing the data needed to resume the flow. */ - public static void setSwitchBrowserBundle(@Nullable final Bundle bundle) { + public static synchronized void setSwitchBrowserBundle(@Nullable final Bundle bundle) { switchBrowserBundle = bundle; } } From 27e54f6f533ee384730adfbdda43d1c8ec5d7e7f Mon Sep 17 00:00:00 2001 From: p3dr0rv Date: Tue, 2 Sep 2025 11:56:56 -0700 Subject: [PATCH 11/11] Enhance SwitchBrowserActivity to manage Custom Tabs lifecycle and improve browser launch logic --- .../providers/oauth2/SwitchBrowserActivity.kt | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt index 1f02b04a13..d1bd9ed564 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivity.kt @@ -24,10 +24,10 @@ package com.microsoft.identity.common.internal.providers.oauth2 import android.content.Intent import android.os.Bundle -import androidx.browser.customtabs.CustomTabsIntent import androidx.fragment.app.FragmentActivity import com.microsoft.identity.common.logging.Logger import androidx.core.net.toUri +import com.microsoft.identity.common.internal.ui.browser.CustomTabsManager /** @@ -63,6 +63,7 @@ class SwitchBrowserActivity : FragmentActivity() { // Flag to track if a Custom Chrome Tab (CCT) has been launched private var cctLaunched = false + private var customTabsManager = CustomTabsManager(this) companion object { private val TAG: String = SwitchBrowserActivity::class.java.simpleName @@ -131,23 +132,24 @@ class SwitchBrowserActivity : FragmentActivity() { "Launching switch browser request on browser: $browserPackageName, Custom Tabs supported: $browserSupportsCustomTabs" ) - // Launch browser based on Custom Tabs support + // Create an intent to launch the browser + val browserIntent: Intent if (browserSupportsCustomTabs) { - // Use Custom Tabs for better user experience and security - Logger.info(methodTag, "Launching Custom Tabs intent for DUNA authentication") - val customTabsIntent = CustomTabsIntent.Builder().build().apply { - intent.setPackage(browserPackageName) + Logger.info(methodTag, "CustomTabsService is supported.") + //create customTabsIntent + if (!customTabsManager.bind(this, browserPackageName)) { + Logger.warn(methodTag, "Failed to bind CustomTabsService.") + browserIntent = Intent(Intent.ACTION_VIEW) + } else { + browserIntent = customTabsManager.customTabsIntent.intent } - customTabsIntent.launchUrl(this, processUri.toUri()) } else { - // Fallback to standard browser intent - Logger.info(methodTag, "Launching standard browser intent for DUNA authentication") - val browserIntent = Intent(Intent.ACTION_VIEW).apply { - data = processUri.toUri() - setPackage(browserPackageName) - } - startActivity(browserIntent) + Logger.warn(methodTag, "CustomTabsService is NOT supported") + browserIntent = Intent(Intent.ACTION_VIEW) } + browserIntent.setPackage(browserPackageName) + browserIntent.setData(processUri.toUri()) + startActivity(browserIntent) } /** @@ -236,4 +238,9 @@ class SwitchBrowserActivity : FragmentActivity() { cctLaunched = true } + + override fun onDestroy() { + super.onDestroy() + customTabsManager.unbind() + } }