Skip to content

Commit f5f8eb3

Browse files
author
Michael Tran
committed
Merge branch '164-fix-loading-of-openurlinnewtab-cancelurl' into 'master'
Resolve "Fix loading of openUrlInNewTab cancelUrl" Closes #164 See merge request pace/mobile/android/pace-cloud-sdk!154
2 parents dc2db5f + 6e6efee commit f5f8eb3

File tree

15 files changed

+157
-103
lines changed

15 files changed

+157
-103
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ x.y.z Release notes (yyyy-MM-dd)
66
<!-- ### Fixes - Include, if needed -->
77
<!-- ### Internal - Include, if needed -->
88

9+
### Fixes
10+
11+
* Refactor `openUrlInNewTab` flow to fix loading of `cancelUrl` when creating a payment method which opens in custom tab
12+
13+
914
9.3.0 Release notes (2021-09-09)
1015
=============================================================
1116

12-
1317
### Enhancements
1418

1519
* Change `requestCofuGasStations()` to return all cofu stations (online and offline)

library/src/main/AndroidManifest.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,20 @@
1313
</queries>
1414

1515
<application>
16+
1617
<activity
1718
android:name=".appkit.app.AppActivity"
1819
android:launchMode="singleTop"
1920
android:screenOrientation="portrait"
2021
android:theme="@style/AppKitTheme" />
2122

2223
<activity
23-
android:name=".appkit.app.RedirectUriReceiverActivity"
24+
android:name=".appkit.app.customtab.CustomTabManagementActivity"
25+
android:exported="false"
26+
android:launchMode="singleTask" />
27+
28+
<activity
29+
android:name=".appkit.app.customtab.RedirectUriReceiverActivity"
2430
android:exported="true">
2531
<intent-filter>
2632
<action android:name="android.intent.action.VIEW" />

library/src/main/java/cloud/pace/sdk/appkit/app/AppActivity.kt

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package cloud.pace.sdk.appkit.app
22

3-
import android.content.Intent
43
import android.os.Bundle
54
import androidx.appcompat.app.AppCompatActivity
65
import androidx.lifecycle.lifecycleScope
@@ -63,13 +62,6 @@ class AppActivity : AppCompatActivity(), CloudSDKKoinComponent {
6362
}
6463
}
6564
}
66-
67-
handleIntent(intent)
68-
}
69-
70-
override fun onNewIntent(intent: Intent?) {
71-
super.onNewIntent(intent)
72-
handleIntent(intent)
7365
}
7466

7567
override fun onBackPressed() {
@@ -85,21 +77,8 @@ class AppActivity : AppCompatActivity(), CloudSDKKoinComponent {
8577
super.onDestroy()
8678
}
8779

88-
private fun handleIntent(intent: Intent?) {
89-
intent ?: return
90-
91-
val appLinkAction = intent.action
92-
val appLinkData = intent.data
93-
if (Intent.ACTION_VIEW == appLinkAction) {
94-
appLinkData?.getQueryParameter(TO)?.let { finalRedirect ->
95-
eventManager.onReceivedRedirect(finalRedirect)
96-
}
97-
}
98-
}
99-
10080
companion object {
10181
const val APP_URL = "APP_URL"
10282
const val BACK_TO_FINISH = "BACK_TO_FINISH"
103-
const val TO = "to"
10483
}
10584
}

library/src/main/java/cloud/pace/sdk/appkit/app/AppFragment.kt

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
package cloud.pace.sdk.appkit.app
22

3+
import android.content.Intent
34
import android.net.Uri
45
import android.os.Bundle
56
import android.view.LayoutInflater
67
import android.view.View
78
import android.view.ViewGroup
89
import androidx.browser.customtabs.CustomTabsIntent
910
import androidx.fragment.app.Fragment
11+
import androidx.lifecycle.lifecycleScope
1012
import cloud.pace.sdk.R
1113
import cloud.pace.sdk.api.utils.InterceptorUtils
1214
import cloud.pace.sdk.appkit.app.AppFragmentViewModelImpl.Companion.CHROME_PACKAGE_NAME
15+
import cloud.pace.sdk.appkit.app.customtab.CustomTabManagementActivity
16+
import cloud.pace.sdk.appkit.app.customtab.CustomTabManagementActivity.Companion.CUSTOM_TABS_INTENT
17+
import cloud.pace.sdk.utils.Canceled
1318
import cloud.pace.sdk.utils.CloudSDKKoinComponent
19+
import cloud.pace.sdk.utils.Ok
20+
import cloud.pace.sdk.utils.getResultFor
1421
import kotlinx.android.synthetic.main.fragment_app.*
22+
import kotlinx.coroutines.Dispatchers
23+
import kotlinx.coroutines.launch
1524
import org.koin.android.viewmodel.ext.android.viewModel
1625

1726
class AppFragment : Fragment(), CloudSDKKoinComponent {
@@ -26,10 +35,10 @@ class AppFragment : Fragment(), CloudSDKKoinComponent {
2635
super.onViewCreated(view, savedInstanceState)
2736

2837
val url = activity?.intent?.extras?.getString(AppActivity.APP_URL)
29-
?: activity?.intent?.data?.getQueryParameter(AppActivity.TO)
38+
?: activity?.intent?.data?.getQueryParameter(CustomTabManagementActivity.TO)
3039
?: throw RuntimeException("Missing app URL")
3140

32-
appWebView.loadApp(this, InterceptorUtils.getUrlWithQueryParams(url))
41+
appWebView.init(this, InterceptorUtils.getUrlWithQueryParams(url))
3342

3443
viewModel.closeEvent.observe(viewLifecycleOwner) {
3544
it.getContentIfNotHandled()?.let {
@@ -38,22 +47,33 @@ class AppFragment : Fragment(), CloudSDKKoinComponent {
3847
}
3948

4049
viewModel.openUrlInNewTab.observe(viewLifecycleOwner) {
41-
it.getContentIfNotHandled()?.let { url ->
50+
it.getContentIfNotHandled()?.let { openURLInNewTabRequest ->
4251
context?.let { context ->
4352
val customTabsIntent = CustomTabsIntent.Builder().build()
4453

4554
if (viewModel.isChromeCustomTabsSupported(context)) {
4655
customTabsIntent.intent.setPackage(CHROME_PACKAGE_NAME)
4756
}
4857

49-
customTabsIntent.launchUrl(context, Uri.parse(url))
50-
}
51-
}
52-
}
58+
customTabsIntent.intent.data = Uri.parse(openURLInNewTabRequest.url)
5359

54-
viewModel.redirectEvent.observe(viewLifecycleOwner) {
55-
it.getContentIfNotHandled()?.let {
56-
appWebView.loadApp(this, it)
60+
lifecycleScope.launch(Dispatchers.Main) {
61+
val intent = Intent(context, CustomTabManagementActivity::class.java)
62+
intent.putExtra(CUSTOM_TABS_INTENT, customTabsIntent.intent)
63+
64+
when (val result = getResultFor(intent)) {
65+
is Ok -> {
66+
val redirectUri = result.data?.data?.toString()
67+
if (redirectUri != null) {
68+
appWebView.loadUrl(redirectUri)
69+
} else {
70+
appWebView.loadUrl(openURLInNewTabRequest.cancelUrl)
71+
}
72+
}
73+
is Canceled -> appWebView.loadUrl(openURLInNewTabRequest.cancelUrl)
74+
}
75+
}
76+
}
5777
}
5878
}
5979
}

library/src/main/java/cloud/pace/sdk/appkit/app/AppFragmentViewModel.kt

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import androidx.lifecycle.Observer
99
import androidx.lifecycle.ViewModel
1010
import cloud.pace.sdk.appkit.communication.AppEventManager
1111
import cloud.pace.sdk.appkit.communication.AppModel
12+
import cloud.pace.sdk.appkit.communication.generated.model.request.OpenURLInNewTabRequest
1213
import cloud.pace.sdk.utils.Event
1314

1415
abstract class AppFragmentViewModel : ViewModel() {
1516

1617
abstract val closeEvent: LiveData<Event<Unit>>
17-
abstract val openUrlInNewTab: LiveData<Event<String>>
18-
abstract val redirectEvent: LiveData<Event<String>>
18+
abstract val openUrlInNewTab: LiveData<Event<OpenURLInNewTabRequest>>
1919

2020
abstract fun isChromeCustomTabsSupported(context: Context): Boolean
2121
}
@@ -26,27 +26,19 @@ class AppFragmentViewModelImpl(
2626
) : AppFragmentViewModel() {
2727

2828
override val closeEvent = MutableLiveData<Event<Unit>>()
29-
override val redirectEvent = MutableLiveData<Event<String>>()
30-
override val openUrlInNewTab = MutableLiveData<Event<String>>()
29+
override val openUrlInNewTab = MutableLiveData<Event<OpenURLInNewTabRequest>>()
3130

3231
private val closeObserver = Observer<Unit> {
3332
closeEvent.value = Event(it)
3433
}
3534

36-
private val openUrlInNewTabObserver = Observer<String> {
35+
private val openUrlInNewTabObserver = Observer<OpenURLInNewTabRequest> {
3736
openUrlInNewTab.value = Event(it)
3837
}
3938

40-
private val redirectObserver = Observer<Event<String>> {
41-
it.getContentIfNotHandled()?.let { redirectUrl ->
42-
redirectEvent.value = Event(redirectUrl)
43-
}
44-
}
45-
4639
init {
4740
appModel.close.observeForever(closeObserver)
4841
appModel.openUrlInNewTab.observeForever(openUrlInNewTabObserver)
49-
eventManager.redirectUrl.observeForever(redirectObserver)
5042
}
5143

5244
override fun isChromeCustomTabsSupported(context: Context): Boolean {
@@ -62,7 +54,6 @@ class AppFragmentViewModelImpl(
6254

6355
appModel.close.removeObserver(closeObserver)
6456
appModel.openUrlInNewTab.removeObserver(openUrlInNewTabObserver)
65-
eventManager.redirectUrl.removeObserver(redirectObserver)
6657
}
6758

6859
companion object {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package cloud.pace.sdk.appkit.app.customtab
2+
3+
import android.app.Activity
4+
import android.content.ActivityNotFoundException
5+
import android.content.Intent
6+
import android.net.Uri
7+
8+
class CustomTabManagementActivity : Activity() {
9+
10+
private var isCustomTabStarted = false
11+
12+
override fun onResume() {
13+
super.onResume()
14+
15+
if (!isCustomTabStarted) {
16+
/*
17+
* If this is the first run of the activity, start the custom tabs intent.
18+
* Note that we do not finish the activity at this point, in order to remain on the back
19+
* stack underneath the custom tabs activity.
20+
*/
21+
val customTabIntent = intent.extras?.getParcelable<Intent>(CUSTOM_TABS_INTENT)
22+
if (customTabIntent != null) {
23+
try {
24+
startActivity(customTabIntent)
25+
isCustomTabStarted = true
26+
} catch (e: ActivityNotFoundException) {
27+
setCanceled()
28+
}
29+
} else {
30+
setCanceled()
31+
}
32+
} else {
33+
/*
34+
* On a subsequent run, it must be determined whether we have returned to this activity
35+
* due to an redirect from custom tab (success) or the user cancelled the flow.
36+
* This can be done by checking whether a redirect URI is available, which would be provided by
37+
* RedirectUriReceiverActivity. If it is not, we have returned here due to the user
38+
* pressing the back button or closes the custom tab.
39+
*/
40+
val redirectUri = intent.data?.getQueryParameter(TO)
41+
if (redirectUri != null) {
42+
setSuccess(redirectUri)
43+
} else {
44+
setCanceled()
45+
}
46+
}
47+
}
48+
49+
private fun setSuccess(redirectUri: String) {
50+
val intent = Intent()
51+
intent.data = Uri.parse(redirectUri)
52+
setResult(RESULT_OK, intent)
53+
finish()
54+
}
55+
56+
private fun setCanceled() {
57+
setResult(RESULT_CANCELED)
58+
finish()
59+
}
60+
61+
override fun onNewIntent(intent: Intent?) {
62+
super.onNewIntent(intent)
63+
setIntent(intent)
64+
}
65+
66+
companion object {
67+
const val CUSTOM_TABS_INTENT = "customTabsIntent"
68+
const val TO = "to"
69+
}
70+
}

library/src/main/java/cloud/pace/sdk/appkit/app/RedirectUriReceiverActivity.kt renamed to library/src/main/java/cloud/pace/sdk/appkit/app/customtab/RedirectUriReceiverActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package cloud.pace.sdk.appkit.app
1+
package cloud.pace.sdk.appkit.app.customtab
22

33
import android.content.Intent
44
import android.os.Bundle
@@ -10,7 +10,7 @@ class RedirectUriReceiverActivity : AppCompatActivity() {
1010
super.onCreate(savedInstanceState)
1111

1212
// Handling the redirect in this way ensures that we can remove the browser custom tab from the back stack
13-
val newIntent = Intent(this, AppActivity::class.java)
13+
val newIntent = Intent(this, CustomTabManagementActivity::class.java)
1414
newIntent.data = intent.data
1515
newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
1616
startActivity(newIntent)

library/src/main/java/cloud/pace/sdk/appkit/app/webview/AppWebView.kt

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ class AppWebView(context: Context, attributeSet: AttributeSet) : RelativeLayout(
3131
loadingIndicator?.visibility = View.VISIBLE
3232
}
3333

34-
private val openUrlObserver = Observer<Event<String>> {
35-
val newUrl = it.getContentIfNotHandled() ?: return@Observer
34+
private val initObserver = Observer<Event<String>> {
35+
val url = it.getContentIfNotHandled() ?: return@Observer
3636

37-
val appWebViewClient = AppWebViewClient(newUrl, webViewModel, context)
37+
val appWebViewClient = AppWebViewClient(url, webViewModel, context)
3838
webView.webViewClient = appWebViewClient
3939
webView.webChromeClient = appWebViewClient.chromeClient
4040

41-
webView?.loadUrl(newUrl)
41+
loadUrl(url)
4242
}
4343

4444
private val isInErrorStateObserver = Observer<Event<Boolean>> {
@@ -107,15 +107,27 @@ class AppWebView(context: Context, attributeSet: AttributeSet) : RelativeLayout(
107107
}
108108

109109
/**
110-
* Sets the app with fragment as parent
110+
* Initializes [AppWebView] with [AppWebViewClient] and loads the [url] in the WebView with the passed [fragment][parent] as parent.
111111
*/
112-
fun loadApp(parent: Fragment, url: String) {
112+
fun init(parent: Fragment, url: String) {
113113
setWebContentsDebuggingEnabled(true)
114114

115115
fragment = parent
116116
webViewModel.init(url)
117117
}
118118

119+
/**
120+
* Loads the [url] in the WebView.
121+
* Also finishes the [cloud.pace.sdk.appkit.app.AppActivity] if the url is [AppWebViewClient.CLOSE_URI].
122+
*/
123+
fun loadUrl(url: String) {
124+
if (url == AppWebViewClient.CLOSE_URI) {
125+
webViewModel.closeApp()
126+
} else {
127+
webView?.loadUrl(url)
128+
}
129+
}
130+
119131
fun onBackPressed() {
120132
if (webView.canGoBack()) {
121133
webView.goBack()
@@ -132,7 +144,7 @@ class AppWebView(context: Context, attributeSet: AttributeSet) : RelativeLayout(
132144
?: throw RuntimeException("lifecycle owner not found ")
133145

134146
// TODO: should this be moved to "onResume" and "onPause" and replaced with "observeForever"?
135-
webViewModel.loadUrl.observe(lifecycleOwner, openUrlObserver)
147+
webViewModel.init.observe(lifecycleOwner, initObserver)
136148
webViewModel.isInErrorState.observe(lifecycleOwner, isInErrorStateObserver)
137149
webViewModel.showLoadingIndicator.observe(lifecycleOwner, showLoadingIndicatorObserver)
138150
webViewModel.biometricRequest.observe(lifecycleOwner, biometricRequestObserver)

library/src/main/java/cloud/pace/sdk/appkit/app/webview/AppWebViewClient.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ class AppWebViewClient(var url: String, val callback: WebClientCallback, val con
8585

8686
override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {
8787
url?.let { callback.onUrlChanged(it) }
88+
89+
if (url == CLOSE_URI) {
90+
callback.onClose()
91+
}
8892
}
8993

9094
@TargetApi(Build.VERSION_CODES.N)

0 commit comments

Comments
 (0)