Skip to content

Commit 602d1d0

Browse files
authored
Allow clients to handle onGeolocationPermissionsShowPrompt() calls (#158)
1 parent 7b8af11 commit 602d1d0

File tree

10 files changed

+142
-14
lines changed

10 files changed

+142
-14
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 3.3.0 December 4, 2024
4+
5+
- Expose [onGeolocationPermissionsShowPrompt()](<https://developer.android.com/reference/android/webkit/WebChromeClient#onGeolocationPermissionsShowPrompt(java.lang.String,%20android.webkit.GeolocationPermissions.Callback)>) and [onGeolocationPermissionsHidePrompt()](<https://developer.android.com/reference/android/webkit/WebChromeClient#onGeolocationPermissionsHidePrompt()>), allowing them to be implemented so customers can use the `Use my location` functionality to find nearby pickup points.
6+
37
## 3.2.2 November 15 2024
48

59
- Updates the proguard rules for the library to prevent minification of essential classes.

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ your project:
5050
#### Gradle
5151

5252
```groovy
53-
implementation "com.shopify:checkout-sheet-kit:3.2.2"
53+
implementation "com.shopify:checkout-sheet-kit:3.3.0"
5454
```
5555

5656
#### Maven
@@ -60,7 +60,7 @@ implementation "com.shopify:checkout-sheet-kit:3.2.2"
6060
<dependency>
6161
<groupId>com.shopify</groupId>
6262
<artifactId>checkout-sheet-kit</artifactId>
63-
<version>3.2.2</version>
63+
<version>3.3.0</version>
6464
</dependency>
6565
```
6666

@@ -304,6 +304,17 @@ val processor = object : DefaultCheckoutEventProcessor(activity) {
304304
// To cancel the request, call filePathCallback.onReceiveValue(null) and return true.
305305
}
306306

307+
308+
override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
309+
// Called to tell the client to show a geolocation permissions prompt as a geolocation permissions
310+
// request has been made.
311+
// Invoked for example if a customer uses `Use my location` for pickup points
312+
}
313+
314+
override fun onGeolocationPermissionsHidePrompt() {
315+
// Called to tell the client to hide the geolocation permissions prompt, e.g. as the request has been cancelled
316+
}
317+
307318
override fun onPermissionRequest(permissionRequest: PermissionRequest) {
308319
// Called when a permission has been requested, e.g. to access the camera
309320
// implement to grant/deny/request permissions.

lib/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def resolveEnvVarValue(name, defaultValue) {
1414
return rawValue ? rawValue : defaultValue
1515
}
1616

17-
def versionName = resolveEnvVarValue("CHECKOUT_SHEET_KIT_VERSION", "3.2.2")
17+
def versionName = resolveEnvVarValue("CHECKOUT_SHEET_KIT_VERSION", "3.3.0")
1818

1919
ext {
2020
app_compat_version = '1.6.1'

lib/src/main/java/com/shopify/checkoutsheetkit/BaseWebView.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import android.view.View
3333
import android.view.ViewGroup
3434
import android.view.ViewGroup.LayoutParams
3535
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
36+
import android.webkit.GeolocationPermissions
3637
import android.webkit.PermissionRequest
3738
import android.webkit.RenderProcessGoneDetail
3839
import android.webkit.ValueCallback
@@ -70,6 +71,15 @@ internal abstract class BaseWebView(context: Context, attributeSet: AttributeSet
7071
super.onProgressChanged(view, newProgress)
7172
getEventProcessor().updateProgressBar(newProgress)
7273
}
74+
75+
override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
76+
getEventProcessor().onGeolocationPermissionsShowPrompt(origin, callback)
77+
}
78+
79+
override fun onGeolocationPermissionsHidePrompt() {
80+
getEventProcessor().onGeolocationPermissionsHidePrompt()
81+
}
82+
7383
override fun onPermissionRequest(request: PermissionRequest) {
7484
getEventProcessor().onPermissionRequest(request)
7585
}

lib/src/main/java/com/shopify/checkoutsheetkit/CheckoutEventProcessor.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import android.annotation.SuppressLint
2626
import android.content.Context
2727
import android.content.Intent
2828
import android.net.Uri
29+
import android.webkit.GeolocationPermissions
2930
import android.webkit.PermissionRequest
3031
import android.webkit.ValueCallback
3132
import android.webkit.WebChromeClient
@@ -83,6 +84,17 @@ public interface CheckoutEventProcessor {
8384
filePathCallback: ValueCallback<Array<Uri>>,
8485
fileChooserParams: WebChromeClient.FileChooserParams,
8586
): Boolean
87+
88+
/**
89+
* Called when the client should show a location permissions prompt. For example when using 'Use my location' for
90+
* pickup points in checkout
91+
*/
92+
public fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback)
93+
94+
/**
95+
* Called when the client should hide the location permissions prompt, e.g. if th request is cancelled
96+
*/
97+
public fun onGeolocationPermissionsHidePrompt()
8698
}
8799

88100
internal class NoopEventProcessor : CheckoutEventProcessor {
@@ -111,6 +123,12 @@ internal class NoopEventProcessor : CheckoutEventProcessor {
111123

112124
override fun onPermissionRequest(permissionRequest: PermissionRequest) {/* noop */
113125
}
126+
127+
override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {/* noop */
128+
}
129+
130+
override fun onGeolocationPermissionsHidePrompt() {/* noop */
131+
}
114132
}
115133

116134
/**
@@ -148,6 +166,14 @@ public abstract class DefaultCheckoutEventProcessor @JvmOverloads constructor(
148166
return false
149167
}
150168

169+
override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
170+
// no-op override to implement
171+
}
172+
173+
override fun onGeolocationPermissionsHidePrompt() {
174+
// no-op override to implement
175+
}
176+
151177
private fun Context.launchEmailApp(to: String) {
152178
val intent = Intent(Intent.ACTION_SEND)
153179
intent.type = "vnd.android.cursor.item/email"

lib/src/main/java/com/shopify/checkoutsheetkit/CheckoutWebViewEventProcessor.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import android.os.Handler
2727
import android.os.Looper
2828
import android.view.View.INVISIBLE
2929
import android.view.View.VISIBLE
30+
import android.webkit.GeolocationPermissions
3031
import android.webkit.PermissionRequest
3132
import android.webkit.ValueCallback
3233
import android.webkit.WebChromeClient.FileChooserParams
@@ -66,6 +67,14 @@ internal class CheckoutWebViewEventProcessor(
6667
}
6768
}
6869

70+
fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
71+
return eventProcessor.onGeolocationPermissionsShowPrompt(origin, callback)
72+
}
73+
74+
fun onGeolocationPermissionsHidePrompt() {
75+
return eventProcessor.onGeolocationPermissionsHidePrompt()
76+
}
77+
6978
fun onShowFileChooser(
7079
webView: WebView,
7180
filePathCallback: ValueCallback<Array<Uri>>,

lib/src/test/java/com/shopify/checkoutsheetkit/CheckoutWebViewTest.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import android.net.Uri
2727
import android.os.Looper
2828
import android.view.View.VISIBLE
2929
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
30+
import android.webkit.GeolocationPermissions
3031
import android.webkit.PermissionRequest
3132
import android.webkit.ValueCallback
3233
import android.webkit.WebChromeClient.FileChooserParams
@@ -272,6 +273,22 @@ class CheckoutWebViewTest {
272273
verify(webViewEventProcessor).onShowFileChooser(view, filePathCallback, fileChooserParams)
273274
}
274275

276+
@Test
277+
fun `calls processors onGeolocationPermissionsShowPrompt when called on webChromeClient`() {
278+
val view = CheckoutWebView.cacheableCheckoutView(URL, activity)
279+
val webViewEventProcessor = mock<CheckoutWebViewEventProcessor>()
280+
view.setEventProcessor(webViewEventProcessor)
281+
282+
val shadow = shadowOf(view)
283+
284+
val callback = mock<GeolocationPermissions.Callback>()
285+
val origin = "origin"
286+
287+
shadow.webChromeClient.onGeolocationPermissionsShowPrompt(origin, callback)
288+
289+
verify(webViewEventProcessor).onGeolocationPermissionsShowPrompt(origin, callback)
290+
}
291+
275292
@Test
276293
fun `should recover from errors`() {
277294
Robolectric.buildActivity(ComponentActivity::class.java).use { activityController ->

samples/MobileBuyIntegration/app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
<uses-permission android:name="android.permission.INTERNET" />
99

10+
<!-- Optional: Allow querying location for pickup points if enabled -->
11+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
12+
1013
<!-- Optional: Example permissions required for certain payment methods provided via 3rd party apps -->
1114
<uses-permission android:name="android.permission.CAMERA" />
1215
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_sdk_mobile_buy_integration_sample/MainActivity.kt

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import android.Manifest
2626
import android.content.pm.PackageManager
2727
import android.net.Uri
2828
import android.os.Bundle
29+
import android.webkit.GeolocationPermissions
2930
import android.webkit.ValueCallback
3031
import android.webkit.WebChromeClient.FileChooserParams
3132
import android.webkit.WebView.setWebContentsDebuggingEnabled
@@ -38,19 +39,34 @@ import timber.log.Timber
3839
import timber.log.Timber.DebugTree
3940

4041
class MainActivity : ComponentActivity() {
41-
42+
// Launchers
4243
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
4344
private lateinit var showFileChooserLauncher: ActivityResultLauncher<FileChooserParams>
45+
private lateinit var geolocationLauncher: ActivityResultLauncher<String>
4446

47+
// State related to file chooser requests (e.g. for using a file chooser/camera for proving identity)
4548
private var filePathCallback: ValueCallback<Array<Uri>>? = null
4649
private var fileChooserParams: FileChooserParams? = null
4750

51+
// State related to geolocation requests (e.g. for pickup points - use my location)
52+
private var geolocationPermissionCallback: GeolocationPermissions.Callback? = null
53+
private var geolocationOrigin: String? = null
54+
4855
override fun onCreate(savedInstanceState: Bundle?) {
4956
super.onCreate(savedInstanceState)
50-
setWebContentsDebuggingEnabled(true)
57+
58+
// Allow debugging the WebView via chrome://inspect
59+
setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
60+
61+
// Setup logging in debug build
62+
if (BuildConfig.DEBUG) {
63+
Timber.plant(DebugTree())
64+
}
65+
5166
setContent {
5267
CheckoutSdkApp()
5368
}
69+
5470
requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
5571
val fileChooserParams = this.fileChooserParams
5672
if (isGranted && fileChooserParams != null) {
@@ -59,28 +75,55 @@ class MainActivity : ComponentActivity() {
5975
}
6076
// N.B. a file chooser intent (without camera) could be launched here if the permission was denied
6177
}
78+
6279
showFileChooserLauncher = registerForActivityResult(FileChooserResultContract()) { uri: Uri? ->
80+
// invoke the callback with the selected file
6381
filePathCallback?.onReceiveValue(if (uri != null) arrayOf(uri) else null)
82+
83+
// reset fileChooser state
6484
filePathCallback = null
85+
fileChooserParams = null
6586
}
6687

67-
if (BuildConfig.DEBUG) {
68-
Timber.plant(DebugTree())
88+
geolocationLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
89+
// invoke the callback with the permission result
90+
geolocationPermissionCallback?.invoke(geolocationOrigin, isGranted, false)
91+
92+
// reset geolocation state
93+
geolocationPermissionCallback = null
94+
geolocationOrigin = null
6995
}
7096
}
7197

7298
// Show a file chooser when prompted by the event processor
7399
fun onShowFileChooser(filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams): Boolean {
74100
this.filePathCallback = filePathCallback
75-
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
76-
// Permissions not yet granted, request before launching chooser
77-
this.fileChooserParams = fileChooserParams
78-
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
79-
} else {
80-
// Permissions already granted, launch chooser
101+
if (permissionAlreadyGranted(Manifest.permission.CAMERA)) {
102+
// Permissions already granted, launch chooser immediately
81103
showFileChooserLauncher.launch(fileChooserParams)
82104
this.fileChooserParams = null
105+
} else {
106+
// Permissions not yet granted, request permission before launching chooser
107+
this.fileChooserParams = fileChooserParams
108+
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
83109
}
84110
return true
85111
}
112+
113+
// Deal with requests from Checkout to show the geolocation permissions prompt
114+
fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
115+
if (permissionAlreadyGranted(Manifest.permission.ACCESS_COARSE_LOCATION)) {
116+
// Permission already granted, invoke callback immediately
117+
callback(origin, true, true)
118+
} else {
119+
// Permission not yet granted, request permission before invoking callback
120+
geolocationPermissionCallback = callback
121+
geolocationOrigin = origin
122+
geolocationLauncher.launch(Manifest.permission.ACCESS_COARSE_LOCATION)
123+
}
124+
}
125+
126+
private fun permissionAlreadyGranted(permission: String): Boolean {
127+
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
128+
}
86129
}

samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_sdk_mobile_buy_integration_sample/common/MobileBuyEventProcessor.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ package com.shopify.checkout_sdk_mobile_buy_integration_sample.common
2424

2525
import android.content.Context
2626
import android.net.Uri
27+
import android.webkit.GeolocationPermissions
2728
import android.webkit.ValueCallback
2829
import android.webkit.WebChromeClient
2930
import android.webkit.WebView
@@ -53,7 +54,7 @@ class MobileBuyEventProcessor(
5354
private val navController: NavController,
5455
private val logger: Logger,
5556
private val context: Context
56-
): DefaultCheckoutEventProcessor(context) {
57+
) : DefaultCheckoutEventProcessor(context) {
5758
override fun onCheckoutCompleted(checkoutCompletedEvent: CheckoutCompletedEvent) {
5859
logger.log(checkoutCompletedEvent)
5960

@@ -83,6 +84,10 @@ class MobileBuyEventProcessor(
8384
logger.log("Checkout canceled")
8485
}
8586

87+
override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
88+
return (context as MainActivity).onGeolocationPermissionsShowPrompt(origin, callback)
89+
}
90+
8691
override fun onShowFileChooser(
8792
webView: WebView,
8893
filePathCallback: ValueCallback<Array<Uri>>,

0 commit comments

Comments
 (0)