Skip to content

Commit 0036f35

Browse files
Merge pull request #773 from qonversion/release-sdk/9.2.1
Qonversion Release 9.2.1
2 parents 6552d64 + b586888 commit 0036f35

4 files changed

Lines changed: 126 additions & 2 deletions

File tree

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask
66
buildscript {
77
ext {
88
sdk = [
9-
versionName: "9.2.0",
9+
versionName: "9.2.1",
1010
versionCode: 1
1111
]
1212
nocodes = [

fastlane/report.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77

8-
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000229">
8+
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000313">
99

1010
</testcase>
1111

sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ internal class QProductCenterManager internal constructor(
424424
requestTrigger,
425425
object : QonversionLaunchCallback {
426426
override fun onSuccess(launchResult: QLaunchResult) {
427+
handleUserSwitchingOnRestore(launchResult)
427428
updateLaunchResult(launchResult)
428429
executeRestoreBlocksOnSuccess(launchResult.permissions.toEntitlementsMap())
429430
}
@@ -487,6 +488,24 @@ internal class QProductCenterManager internal constructor(
487488

488489
// Private functions
489490

491+
private fun handleUserSwitchingOnRestore(launchResult: QLaunchResult) {
492+
val currentUserId = userInfoService.obtainUserId()
493+
val newUserId = launchResult.uid
494+
495+
if (newUserId.isEmpty() || newUserId == currentUserId) {
496+
return
497+
}
498+
499+
logger.debug(
500+
"restore() -> User switch detected. Switching from $currentUserId to $newUserId"
501+
)
502+
503+
userInfoService.storeQonversionUserId(newUserId)
504+
internalConfig.uid = newUserId
505+
remoteConfigManager.onUserUpdate()
506+
launchResultCache.clearPermissionsCache()
507+
}
508+
490509
private fun calculateRestorePermissionsLocally(
491510
purchases: List<Purchase>,
492511
restoreError: QonversionError

sdk/src/test/java/com/qonversion/android/sdk/internal/QProductCenterManagerTest.kt

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import android.os.Build
77
import com.android.billingclient.api.BillingClient
88
import com.android.billingclient.api.Purchase
99
import com.qonversion.android.sdk.dto.QPurchaseOptions
10+
import com.qonversion.android.sdk.dto.QonversionError
11+
import com.qonversion.android.sdk.dto.QonversionErrorCode
12+
import com.qonversion.android.sdk.listeners.QonversionEntitlementsCallback
1013
import com.qonversion.android.sdk.listeners.QonversionLaunchCallback
14+
import com.qonversion.android.sdk.internal.api.RequestTrigger
1115
import com.qonversion.android.sdk.internal.billing.BillingError
1216
import com.qonversion.android.sdk.internal.billing.QonversionBillingService
1317
import com.qonversion.android.sdk.internal.dto.QLaunchResult
@@ -153,6 +157,107 @@ internal class QProductCenterManagerTest {
153157
)
154158
}
155159

160+
// User switching on restore tests
161+
162+
@Test
163+
fun `restore with same uid should not trigger user switch`() {
164+
val currentUid = "user_old"
165+
val launchResult = QLaunchResult(currentUid, Date(), offerings = null)
166+
167+
every { mockUserInfoService.obtainUserId() } returns currentUid
168+
mockRestoreFlow(launchResult)
169+
170+
val callback = mockk<QonversionEntitlementsCallback>(relaxed = true)
171+
productCenterManager.restore(RequestTrigger.Restore, callback)
172+
173+
verify(exactly = 0) { mockUserInfoService.storeQonversionUserId(any()) }
174+
verify(exactly = 0) { mockRemoteConfigManager.onUserUpdate() }
175+
verify(exactly = 0) { mockLaunchResultCacheWrapper.clearPermissionsCache() }
176+
verify { callback.onSuccess(any()) }
177+
}
178+
179+
@Test
180+
fun `restore with different uid should trigger user switch`() {
181+
val currentUid = "user_new"
182+
val originalOwnerUid = "user_old"
183+
val launchResult = QLaunchResult(originalOwnerUid, Date(), offerings = null)
184+
185+
every { mockUserInfoService.obtainUserId() } returns currentUid
186+
mockRestoreFlow(launchResult)
187+
188+
val callback = mockk<QonversionEntitlementsCallback>(relaxed = true)
189+
productCenterManager.restore(RequestTrigger.Restore, callback)
190+
191+
verifyOrder {
192+
mockUserInfoService.storeQonversionUserId(originalOwnerUid)
193+
mockConfig.uid = originalOwnerUid
194+
mockRemoteConfigManager.onUserUpdate()
195+
mockLaunchResultCacheWrapper.clearPermissionsCache()
196+
}
197+
verify { callback.onSuccess(any()) }
198+
verify { mockLogger.debug(match { it.contains("User switch detected") }) }
199+
}
200+
201+
@Test
202+
fun `restore with error should not trigger user switch`() {
203+
every { mockBillingService.queryPurchases(any(), captureLambda()) } answers {
204+
lambda<(List<Purchase>) -> Unit>().captured.invoke(
205+
listOf(mockPurchase(Purchase.PurchaseState.PURCHASED, false))
206+
)
207+
}
208+
every { mockBillingService.consumePurchases(any()) } just Runs
209+
210+
val callbackSlot = slot<QonversionLaunchCallback>()
211+
every {
212+
mockRepository.restore(any(), any(), any(), capture(callbackSlot))
213+
} answers {
214+
callbackSlot.captured.onError(
215+
QonversionError(QonversionErrorCode.BackendError)
216+
)
217+
}
218+
219+
val callback = mockk<QonversionEntitlementsCallback>(relaxed = true)
220+
productCenterManager.restore(RequestTrigger.Restore, callback)
221+
222+
verify(exactly = 0) { mockUserInfoService.storeQonversionUserId(any()) }
223+
verify(exactly = 0) { mockRemoteConfigManager.onUserUpdate() }
224+
verify(exactly = 0) { mockLaunchResultCacheWrapper.clearPermissionsCache() }
225+
verify { callback.onError(any()) }
226+
}
227+
228+
@Test
229+
fun `restore with empty uid in response should not trigger user switch`() {
230+
val currentUid = "user_current"
231+
val launchResult = QLaunchResult("", Date(), offerings = null)
232+
233+
every { mockUserInfoService.obtainUserId() } returns currentUid
234+
mockRestoreFlow(launchResult)
235+
236+
val callback = mockk<QonversionEntitlementsCallback>(relaxed = true)
237+
productCenterManager.restore(RequestTrigger.Restore, callback)
238+
239+
verify(exactly = 0) { mockUserInfoService.storeQonversionUserId(any()) }
240+
verify(exactly = 0) { mockRemoteConfigManager.onUserUpdate() }
241+
verify(exactly = 0) { mockLaunchResultCacheWrapper.clearPermissionsCache() }
242+
verify { callback.onSuccess(any()) }
243+
}
244+
245+
private fun mockRestoreFlow(launchResult: QLaunchResult) {
246+
every { mockBillingService.queryPurchases(any(), captureLambda()) } answers {
247+
lambda<(List<Purchase>) -> Unit>().captured.invoke(
248+
listOf(mockPurchase(Purchase.PurchaseState.PURCHASED, false))
249+
)
250+
}
251+
every { mockBillingService.consumePurchases(any()) } just Runs
252+
253+
val callbackSlot = slot<QonversionLaunchCallback>()
254+
every {
255+
mockRepository.restore(any(), any(), any(), capture(callbackSlot))
256+
} answers {
257+
callbackSlot.captured.onSuccess(launchResult)
258+
}
259+
}
260+
156261
private fun mockPurchase(
157262
@Purchase.PurchaseState purchaseState: Int,
158263
isAcknowledged: Boolean

0 commit comments

Comments
 (0)