Skip to content

Commit

Permalink
Allow clients to handle onGeolocationPermissionsShowPrompt() calls (#158
Browse files Browse the repository at this point in the history
)
  • Loading branch information
kiftio authored Dec 4, 2024
1 parent 7b8af11 commit 602d1d0
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 14 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 3.3.0 December 4, 2024

- 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.

## 3.2.2 November 15 2024

- Updates the proguard rules for the library to prevent minification of essential classes.
Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ your project:
#### Gradle

```groovy
implementation "com.shopify:checkout-sheet-kit:3.2.2"
implementation "com.shopify:checkout-sheet-kit:3.3.0"
```

#### Maven
Expand All @@ -60,7 +60,7 @@ implementation "com.shopify:checkout-sheet-kit:3.2.2"
<dependency>
<groupId>com.shopify</groupId>
<artifactId>checkout-sheet-kit</artifactId>
<version>3.2.2</version>
<version>3.3.0</version>
</dependency>
```

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


override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
// Called to tell the client to show a geolocation permissions prompt as a geolocation permissions
// request has been made.
// Invoked for example if a customer uses `Use my location` for pickup points
}

override fun onGeolocationPermissionsHidePrompt() {
// Called to tell the client to hide the geolocation permissions prompt, e.g. as the request has been cancelled
}

override fun onPermissionRequest(permissionRequest: PermissionRequest) {
// Called when a permission has been requested, e.g. to access the camera
// implement to grant/deny/request permissions.
Expand Down
2 changes: 1 addition & 1 deletion lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def resolveEnvVarValue(name, defaultValue) {
return rawValue ? rawValue : defaultValue
}

def versionName = resolveEnvVarValue("CHECKOUT_SHEET_KIT_VERSION", "3.2.2")
def versionName = resolveEnvVarValue("CHECKOUT_SHEET_KIT_VERSION", "3.3.0")

ext {
app_compat_version = '1.6.1'
Expand Down
10 changes: 10 additions & 0 deletions lib/src/main/java/com/shopify/checkoutsheetkit/BaseWebView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.webkit.GeolocationPermissions
import android.webkit.PermissionRequest
import android.webkit.RenderProcessGoneDetail
import android.webkit.ValueCallback
Expand Down Expand Up @@ -70,6 +71,15 @@ internal abstract class BaseWebView(context: Context, attributeSet: AttributeSet
super.onProgressChanged(view, newProgress)
getEventProcessor().updateProgressBar(newProgress)
}

override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
getEventProcessor().onGeolocationPermissionsShowPrompt(origin, callback)
}

override fun onGeolocationPermissionsHidePrompt() {
getEventProcessor().onGeolocationPermissionsHidePrompt()
}

override fun onPermissionRequest(request: PermissionRequest) {
getEventProcessor().onPermissionRequest(request)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.webkit.GeolocationPermissions
import android.webkit.PermissionRequest
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
Expand Down Expand Up @@ -83,6 +84,17 @@ public interface CheckoutEventProcessor {
filePathCallback: ValueCallback<Array<Uri>>,
fileChooserParams: WebChromeClient.FileChooserParams,
): Boolean

/**
* Called when the client should show a location permissions prompt. For example when using 'Use my location' for
* pickup points in checkout
*/
public fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback)

/**
* Called when the client should hide the location permissions prompt, e.g. if th request is cancelled
*/
public fun onGeolocationPermissionsHidePrompt()
}

internal class NoopEventProcessor : CheckoutEventProcessor {
Expand Down Expand Up @@ -111,6 +123,12 @@ internal class NoopEventProcessor : CheckoutEventProcessor {

override fun onPermissionRequest(permissionRequest: PermissionRequest) {/* noop */
}

override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {/* noop */
}

override fun onGeolocationPermissionsHidePrompt() {/* noop */
}
}

/**
Expand Down Expand Up @@ -148,6 +166,14 @@ public abstract class DefaultCheckoutEventProcessor @JvmOverloads constructor(
return false
}

override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
// no-op override to implement
}

override fun onGeolocationPermissionsHidePrompt() {
// no-op override to implement
}

private fun Context.launchEmailApp(to: String) {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "vnd.android.cursor.item/email"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import android.os.Handler
import android.os.Looper
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.webkit.GeolocationPermissions
import android.webkit.PermissionRequest
import android.webkit.ValueCallback
import android.webkit.WebChromeClient.FileChooserParams
Expand Down Expand Up @@ -66,6 +67,14 @@ internal class CheckoutWebViewEventProcessor(
}
}

fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
return eventProcessor.onGeolocationPermissionsShowPrompt(origin, callback)
}

fun onGeolocationPermissionsHidePrompt() {
return eventProcessor.onGeolocationPermissionsHidePrompt()
}

fun onShowFileChooser(
webView: WebView,
filePathCallback: ValueCallback<Array<Uri>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import android.net.Uri
import android.os.Looper
import android.view.View.VISIBLE
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.webkit.GeolocationPermissions
import android.webkit.PermissionRequest
import android.webkit.ValueCallback
import android.webkit.WebChromeClient.FileChooserParams
Expand Down Expand Up @@ -272,6 +273,22 @@ class CheckoutWebViewTest {
verify(webViewEventProcessor).onShowFileChooser(view, filePathCallback, fileChooserParams)
}

@Test
fun `calls processors onGeolocationPermissionsShowPrompt when called on webChromeClient`() {
val view = CheckoutWebView.cacheableCheckoutView(URL, activity)
val webViewEventProcessor = mock<CheckoutWebViewEventProcessor>()
view.setEventProcessor(webViewEventProcessor)

val shadow = shadowOf(view)

val callback = mock<GeolocationPermissions.Callback>()
val origin = "origin"

shadow.webChromeClient.onGeolocationPermissionsShowPrompt(origin, callback)

verify(webViewEventProcessor).onGeolocationPermissionsShowPrompt(origin, callback)
}

@Test
fun `should recover from errors`() {
Robolectric.buildActivity(ComponentActivity::class.java).use { activityController ->
Expand Down
3 changes: 3 additions & 0 deletions samples/MobileBuyIntegration/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

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

<!-- Optional: Allow querying location for pickup points if enabled -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<!-- Optional: Example permissions required for certain payment methods provided via 3rd party apps -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import android.Manifest
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.webkit.GeolocationPermissions
import android.webkit.ValueCallback
import android.webkit.WebChromeClient.FileChooserParams
import android.webkit.WebView.setWebContentsDebuggingEnabled
Expand All @@ -38,19 +39,34 @@ import timber.log.Timber
import timber.log.Timber.DebugTree

class MainActivity : ComponentActivity() {

// Launchers
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
private lateinit var showFileChooserLauncher: ActivityResultLauncher<FileChooserParams>
private lateinit var geolocationLauncher: ActivityResultLauncher<String>

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

// State related to geolocation requests (e.g. for pickup points - use my location)
private var geolocationPermissionCallback: GeolocationPermissions.Callback? = null
private var geolocationOrigin: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setWebContentsDebuggingEnabled(true)

// Allow debugging the WebView via chrome://inspect
setWebContentsDebuggingEnabled(BuildConfig.DEBUG)

// Setup logging in debug build
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
}

setContent {
CheckoutSdkApp()
}

requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
val fileChooserParams = this.fileChooserParams
if (isGranted && fileChooserParams != null) {
Expand All @@ -59,28 +75,55 @@ class MainActivity : ComponentActivity() {
}
// N.B. a file chooser intent (without camera) could be launched here if the permission was denied
}

showFileChooserLauncher = registerForActivityResult(FileChooserResultContract()) { uri: Uri? ->
// invoke the callback with the selected file
filePathCallback?.onReceiveValue(if (uri != null) arrayOf(uri) else null)

// reset fileChooser state
filePathCallback = null
fileChooserParams = null
}

if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
geolocationLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
// invoke the callback with the permission result
geolocationPermissionCallback?.invoke(geolocationOrigin, isGranted, false)

// reset geolocation state
geolocationPermissionCallback = null
geolocationOrigin = null
}
}

// Show a file chooser when prompted by the event processor
fun onShowFileChooser(filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams): Boolean {
this.filePathCallback = filePathCallback
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// Permissions not yet granted, request before launching chooser
this.fileChooserParams = fileChooserParams
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
} else {
// Permissions already granted, launch chooser
if (permissionAlreadyGranted(Manifest.permission.CAMERA)) {
// Permissions already granted, launch chooser immediately
showFileChooserLauncher.launch(fileChooserParams)
this.fileChooserParams = null
} else {
// Permissions not yet granted, request permission before launching chooser
this.fileChooserParams = fileChooserParams
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
return true
}

// Deal with requests from Checkout to show the geolocation permissions prompt
fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
if (permissionAlreadyGranted(Manifest.permission.ACCESS_COARSE_LOCATION)) {
// Permission already granted, invoke callback immediately
callback(origin, true, true)
} else {
// Permission not yet granted, request permission before invoking callback
geolocationPermissionCallback = callback
geolocationOrigin = origin
geolocationLauncher.launch(Manifest.permission.ACCESS_COARSE_LOCATION)
}
}

private fun permissionAlreadyGranted(permission: String): Boolean {
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ package com.shopify.checkout_sdk_mobile_buy_integration_sample.common

import android.content.Context
import android.net.Uri
import android.webkit.GeolocationPermissions
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebView
Expand Down Expand Up @@ -53,7 +54,7 @@ class MobileBuyEventProcessor(
private val navController: NavController,
private val logger: Logger,
private val context: Context
): DefaultCheckoutEventProcessor(context) {
) : DefaultCheckoutEventProcessor(context) {
override fun onCheckoutCompleted(checkoutCompletedEvent: CheckoutCompletedEvent) {
logger.log(checkoutCompletedEvent)

Expand Down Expand Up @@ -83,6 +84,10 @@ class MobileBuyEventProcessor(
logger.log("Checkout canceled")
}

override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
return (context as MainActivity).onGeolocationPermissionsShowPrompt(origin, callback)
}

override fun onShowFileChooser(
webView: WebView,
filePathCallback: ValueCallback<Array<Uri>>,
Expand Down

0 comments on commit 602d1d0

Please sign in to comment.