Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ The changelog for `Superwall`. Also see the [releases](https://github.com/superw

### Fixes
- Fix handling of deep links when paywall is detached
C
- Enables permission granting from paywall and callbacks

## 2.6.6

## Enhancements
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>


<application
android:name=".MainApplication"
Expand Down
9 changes: 9 additions & 0 deletions superwall/src/main/java/com/superwall/sdk/Superwall.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import com.superwall.sdk.paywall.view.webview.messaging.PaywallWebEvent.Initiate
import com.superwall.sdk.paywall.view.webview.messaging.PaywallWebEvent.OpenedDeepLink
import com.superwall.sdk.paywall.view.webview.messaging.PaywallWebEvent.OpenedURL
import com.superwall.sdk.paywall.view.webview.messaging.PaywallWebEvent.OpenedUrlInChrome
import com.superwall.sdk.paywall.view.webview.messaging.PaywallWebEvent.RequestPermission
import com.superwall.sdk.storage.LatestCustomerInfo
import com.superwall.sdk.storage.ReviewCount
import com.superwall.sdk.storage.ReviewData
Expand Down Expand Up @@ -1408,6 +1409,14 @@ class Superwall(
)
}
}

is RequestPermission -> {
Logger.debug(
LogLevel.debug,
LogScope.paywallView,
message = "Permission requested: ${paywallEvent.permissionType.rawValue}",
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,51 @@ sealed class InternalSuperwallEvent(
}
}

data class PermissionRequested(
val permissionName: String,
val paywallIdentifier: String,
) : InternalSuperwallEvent(
SuperwallEvent.PermissionRequested(permissionName, paywallIdentifier),
) {
override val audienceFilterParams: Map<String, Any> = emptyMap()

override suspend fun getSuperwallParameters(): Map<String, Any> =
mapOf(
"permission_name" to permissionName,
"paywall_identifier" to paywallIdentifier,
)
}

data class PermissionGranted(
val permissionName: String,
val paywallIdentifier: String,
) : InternalSuperwallEvent(
SuperwallEvent.PermissionGranted(permissionName, paywallIdentifier),
) {
override val audienceFilterParams: Map<String, Any> = emptyMap()

override suspend fun getSuperwallParameters(): Map<String, Any> =
mapOf(
"permission_name" to permissionName,
"paywall_identifier" to paywallIdentifier,
)
}

data class PermissionDenied(
val permissionName: String,
val paywallIdentifier: String,
) : InternalSuperwallEvent(
SuperwallEvent.PermissionDenied(permissionName, paywallIdentifier),
) {
override val audienceFilterParams: Map<String, Any> = emptyMap()

override suspend fun getSuperwallParameters(): Map<String, Any> =
mapOf(
"permission_name" to permissionName,
"paywall_identifier" to paywallIdentifier,
)
}

data class PaywallPreload(
val state: State,
val paywallCount: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,33 @@ sealed class SuperwallEvent {
get() = SuperwallEvents.CustomerInfoDidChange.rawName
}

// / When a permission is requested from a paywall.
data class PermissionRequested(
val permissionName: String,
val paywallIdentifier: String,
) : SuperwallEvent() {
override val rawName: String
get() = SuperwallEvents.PermissionRequested.rawName
}

// / When a permission is granted after being requested from a paywall.
data class PermissionGranted(
val permissionName: String,
val paywallIdentifier: String,
) : SuperwallEvent() {
override val rawName: String
get() = SuperwallEvents.PermissionGranted.rawName
}

// / When a permission is denied after being requested from a paywall.
data class PermissionDenied(
val permissionName: String,
val paywallIdentifier: String,
) : SuperwallEvent() {
override val rawName: String
get() = SuperwallEvents.PermissionDenied.rawName
}

// / When paywall preloading starts.
data class PaywallPreloadStart(
val paywallCount: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ enum class SuperwallEvents(
ReviewDenied("review_denied"),
IntegrationAttributes("integration_attributes"),
CustomerInfoDidChange("customerInfo_didChange"),
PermissionRequested("permission_requested"),
PermissionGranted("permission_granted"),
PermissionDenied("permission_denied"),
PaywallPreloadStart("paywallPreload_start"),
PaywallPreloadComplete("paywallPreload_complete"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ import com.superwall.sdk.paywall.view.webview.messaging.PaywallMessageHandler
import com.superwall.sdk.paywall.view.webview.templating.models.JsonVariables
import com.superwall.sdk.paywall.view.webview.templating.models.Variables
import com.superwall.sdk.paywall.view.webview.webViewExists
import com.superwall.sdk.permissions.UserPermissions
import com.superwall.sdk.permissions.UserPermissionsImpl
import com.superwall.sdk.review.MockReviewManager
import com.superwall.sdk.review.ReviewManager
import com.superwall.sdk.review.ReviewManagerImpl
Expand Down Expand Up @@ -187,6 +189,7 @@ class DependencyContainer(
val transactionManager: TransactionManager
val googleBillingWrapper: GoogleBillingWrapper
internal val reviewManager: ReviewManager
internal val userPermissions: UserPermissions

var entitlements: Entitlements
internal lateinit var customerInfoManager: CustomerInfoManager
Expand Down Expand Up @@ -593,6 +596,8 @@ class DependencyContainer(
})
}

userPermissions = UserPermissionsImpl(context)

deepLinkRouter =
DeepLinkRouter(
reedemer,
Expand Down Expand Up @@ -702,6 +707,8 @@ class DependencyContainer(
encodeToB64 = {
Base64.encodeToString(it.toByteArray(StandardCharsets.UTF_8), Base64.NO_WRAP)
},
userPermissions = userPermissions,
getActivity = { activityProvider?.getCurrentActivity() },
)

val state =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ class SWWebView(
addJavascriptInterface(messageHandler, "SWAndroid")

val webSettings = this.settings
setWebContentsDebuggingEnabled(false)
setWebContentsDebuggingEnabled(true)
webSettings.javaScriptEnabled = true
webSettings.setSupportZoom(false)
webSettings.builtInZoomControls = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.superwall.sdk.logger.LogLevel
import com.superwall.sdk.logger.LogScope
import com.superwall.sdk.logger.Logger
import com.superwall.sdk.models.paywall.LocalNotificationType
import com.superwall.sdk.permissions.PermissionType
import com.superwall.sdk.storage.core_data.convertFromJsonElement
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
Expand Down Expand Up @@ -108,6 +109,11 @@ sealed class PaywallMessage {
val body: String,
val delay: Long,
) : PaywallMessage()

data class RequestPermission(
val permissionType: PermissionType,
val requestId: String,
) : PaywallMessage()
}

fun parseWrappedPaywallMessages(jsonString: String): Result<WrappedPaywallMessages> =
Expand Down Expand Up @@ -198,6 +204,22 @@ private fun parsePaywallMessage(json: JsonObject): PaywallMessage {
delay = json["delay"]?.jsonPrimitive?.longOrNull ?: 0L,
)

"request_permission" -> {
val permissionTypeRaw =
json["permission_type"]?.jsonPrimitive?.contentOrNull
?: throw IllegalArgumentException("request_permission missing permission_type")
val permissionType =
PermissionType.fromRaw(permissionTypeRaw)
?: throw IllegalArgumentException("Unknown permission_type: $permissionTypeRaw")
val requestId =
json["request_id"]?.jsonPrimitive?.contentOrNull
?: throw IllegalArgumentException("request_permission missing request_id")
PaywallMessage.RequestPermission(
permissionType = permissionType,
requestId = requestId,
)
}

else -> {
throw IllegalArgumentException("Unknown event name: $eventName")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.superwall.sdk.paywall.view.webview.messaging

import TemplateLogic
import android.app.Activity
import android.webkit.JavascriptInterface
import com.superwall.sdk.analytics.internal.trackable.InternalSuperwallEvent
import com.superwall.sdk.analytics.internal.trackable.TrackableSuperwallEvent
Expand All @@ -17,6 +18,8 @@ import com.superwall.sdk.paywall.view.PaywallView
import com.superwall.sdk.paywall.view.PaywallViewState
import com.superwall.sdk.paywall.view.delegate.PaywallLoadingState
import com.superwall.sdk.paywall.view.webview.SendPaywallMessages
import com.superwall.sdk.permissions.PermissionStatus
import com.superwall.sdk.permissions.UserPermissions
import com.superwall.sdk.storage.core_data.convertToJsonElement
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -63,6 +66,8 @@ class PaywallMessageHandler(
private val ioScope: CoroutineScope,
private val json: Json = Json { encodeDefaults = true },
private val encodeToB64: (String) -> String,
private val userPermissions: UserPermissions,
private val getActivity: () -> Activity?,
) : SendPaywallMessages {
private companion object {
val selectionString =
Expand Down Expand Up @@ -226,6 +231,8 @@ class PaywallMessageHandler(
),
)

is PaywallMessage.RequestPermission -> handleRequestPermission(message)

else -> {
Logger.debug(
LogLevel.error,
Expand Down Expand Up @@ -487,6 +494,127 @@ class PaywallMessageHandler(
)
}

private fun handleRequestPermission(request: PaywallMessage.RequestPermission) {
val activity = getActivity()
val paywallIdentifier = messageHandler?.state?.paywall?.identifier ?: ""
val permissionName = request.permissionType.rawValue

messageHandler?.eventDidOccur(
PaywallWebEvent.RequestPermission(
permissionType = request.permissionType,
requestId = request.requestId,
),
)

// Track permission requested event
ioScope.launch {
track(
InternalSuperwallEvent.PermissionRequested(
permissionName = permissionName,
paywallIdentifier = paywallIdentifier,
),
)
}

if (activity == null) {
Logger.debug(
LogLevel.error,
LogScope.superwallCore,
"Cannot request permission - no activity available",
)
// Send unsupported status back to webview since we can't request
ioScope.launch {
sendPermissionResult(
requestId = request.requestId,
permissionType = request.permissionType,
status = PermissionStatus.UNSUPPORTED,
)
}
return
}

ioScope.launch {
val status =
try {
userPermissions.requestPermission(activity, request.permissionType)
} catch (e: Exception) {
Logger.debug(
LogLevel.error,
LogScope.superwallCore,
"Error requesting permission: ${e.message}",
error = e,
)
PermissionStatus.UNSUPPORTED
}

// Track permission result event
when (status) {
PermissionStatus.GRANTED -> {
track(
InternalSuperwallEvent.PermissionGranted(
permissionName = permissionName,
paywallIdentifier = paywallIdentifier,
),
)
}
PermissionStatus.DENIED, PermissionStatus.UNSUPPORTED -> {
track(
InternalSuperwallEvent.PermissionDenied(
permissionName = permissionName,
paywallIdentifier = paywallIdentifier,
),
)
}
}

sendPermissionResult(
requestId = request.requestId,
permissionType = request.permissionType,
status = status,
)
}
}

/**
* Send a permission_result message back to the webview
*/
private suspend fun sendPermissionResult(
requestId: String,
permissionType: com.superwall.sdk.permissions.PermissionType,
status: PermissionStatus,
) {
val eventList =
listOf(
mapOf(
"event_name" to "permission_result",
"permission_type" to permissionType.rawValue,
"request_id" to requestId,
"status" to status.rawValue,
),
)

val jsonString =
try {
json.encodeToString(eventList.convertToJsonElement())
} catch (e: Throwable) {
Logger.debug(
LogLevel.error,
LogScope.superwallCore,
"Error encoding permission result: ${e.message}",
error = e,
)
return
}

Logger.debug(
LogLevel.debug,
LogScope.superwallCore,
"Sending permission_result: $jsonString",
)

passMessageToWebView(base64String = encodeToB64(jsonString))
}

private fun detectHiddenPaywallEvent(
eventName: String,
userInfo: Map<String, Any>? = null,
Expand Down
Loading
Loading