Skip to content

Commit 5b7826d

Browse files
authored
Merge pull request #886 from CleverTap/develop
Release Huawei 1.5.1
2 parents 5822d73 + 7d5d477 commit 5b7826d

File tree

7 files changed

+248
-21
lines changed

7 files changed

+248
-21
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
## CHANGE LOG.
2+
### September 29, 2025
3+
* [CleverTap Huawei Push SDK v1.5.1](docs/CTHUAWEIPUSHCHANGELOG.md)
4+
25
### September 11, 2025
36
* [CleverTap Android SDK v7.5.2](docs/CTCORECHANGELOG.md)
47

clevertap-hms/src/main/java/com/clevertap/android/hms/HmsSdkHandler.java

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
import android.content.Context;
99
import android.text.TextUtils;
10+
1011
import com.clevertap.android.sdk.CleverTapInstanceConfig;
12+
import com.huawei.agconnect.AGConnectInstance;
1113
import com.huawei.agconnect.AGConnectOptions;
1214
import com.huawei.agconnect.AGConnectOptionsBuilder;
1315
import com.huawei.hms.aaid.HmsInstanceId;
@@ -21,34 +23,60 @@
2123
class HmsSdkHandler implements IHmsSdkHandler {
2224

2325
private final Context context;
24-
2526
private final CleverTapInstanceConfig mConfig;
26-
27-
private final AGConnectOptions options ;
27+
private volatile AGConnectOptions options;
2828

2929
HmsSdkHandler(final Context context, final CleverTapInstanceConfig config) {
3030
this.context = context.getApplicationContext();
31-
mConfig = config;
32-
options = new AGConnectOptionsBuilder().build(context);
31+
this.mConfig = config;
3332
}
3433

3534
@TestOnly
36-
HmsSdkHandler(final Context context, final CleverTapInstanceConfig config,final AGConnectOptions op) {
35+
HmsSdkHandler(final Context context, final CleverTapInstanceConfig config, final AGConnectOptions op) {
3736
this.context = context.getApplicationContext();
3837
mConfig = config;
3938
options = op;
4039
}
4140

41+
private synchronized void initializeOptions() {
42+
if (options != null) {
43+
return;
44+
}
45+
try {
46+
// Try existing AGConnect instance first
47+
AGConnectInstance instance = AGConnectInstance.getInstance();
48+
if (instance != null && instance.getOptions() != null) {
49+
options = instance.getOptions();
50+
return;
51+
}
52+
} catch (Exception e) {
53+
mConfig.log(LOG_TAG, HMS_LOG_TAG + "Failed to initialize AGConnect options: " + e.getMessage());
54+
}
55+
56+
// Fallback to building new options
57+
try {
58+
options = new AGConnectOptionsBuilder().build(context);
59+
} catch (Exception e) {
60+
mConfig.log(LOG_TAG, HMS_LOG_TAG + "Failed to build new AGConnect options: " + e.getMessage());
61+
}
62+
}
4263

4364
@Override
4465
public String appId() {
45-
String appId = null;
66+
if (options == null) {
67+
initializeOptions();
68+
}
69+
70+
if (options == null) {
71+
return null;
72+
}
73+
4674
try {
47-
appId = options.getString(APP_ID_KEY);
75+
return options.getString(APP_ID_KEY);
4876
} catch (Throwable t) {
49-
mConfig.log(LOG_TAG, HMS_LOG_TAG + "HMS availability check failed.");
77+
mConfig.log(LOG_TAG, HMS_LOG_TAG + "Failed to get HMS app ID: " + t.getMessage());
78+
return null;
5079
}
51-
return appId;
5280
}
5381

5482
@Override

clevertap-hms/src/test/java/com/clevertap/android/hms/HmsHandlerTest.kt

Lines changed: 199 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
package com.clevertap.android.hms
22

3+
import com.clevertap.android.hms.HmsConstants.APP_ID_KEY
4+
import com.clevertap.android.hms.HmsConstants.HCM_SCOPE
35
import com.clevertap.android.shared.test.BaseTestCase
46
import com.clevertap.android.shared.test.TestApplication
7+
import com.huawei.agconnect.AGConnectInstance
58
import com.huawei.agconnect.AGConnectOptions
69
import com.huawei.agconnect.AGConnectOptionsBuilder
710
import com.huawei.hms.aaid.HmsInstanceId
811
import com.huawei.hms.api.HuaweiApiAvailability
912
import io.mockk.every
1013
import io.mockk.mockk
14+
import io.mockk.mockkConstructor
1115
import io.mockk.mockkStatic
1216
import io.mockk.spyk
17+
import io.mockk.verify
1318
import org.junit.Assert
1419
import org.junit.Before
1520
import org.junit.Test
1621
import org.junit.runner.RunWith
1722
import org.robolectric.RobolectricTestRunner
1823
import org.robolectric.annotation.Config
24+
import kotlin.test.assertEquals
25+
import kotlin.test.assertFalse
26+
import kotlin.test.assertNull
27+
import kotlin.test.assertTrue
1928

2029
@RunWith(RobolectricTestRunner::class)
2130
@Config(sdk = [28], application = TestApplication::class)
@@ -38,27 +47,27 @@ class HmsHandlerTest : BaseTestCase() {
3847

3948
@Test
4049
fun testAppId_Invalid() {
41-
every { agConnectOptionsSpy.getString(HmsConstants.APP_ID_KEY) } throws RuntimeException("Something went wrong")
50+
every { agConnectOptionsSpy.getString(APP_ID_KEY) } throws RuntimeException("Something went wrong")
4251
val appId = sdkHandler.appId()
4352
Assert.assertNull(appId)
4453
}
4554

4655
@Test
4756
fun testAppId_Valid() {
48-
every { agConnectOptionsSpy.getString(HmsConstants.APP_ID_KEY) } returns HmsTestConstants.HMS_APP_ID
57+
every { agConnectOptionsSpy.getString(APP_ID_KEY) } returns HmsTestConstants.HMS_APP_ID
4958
val appId = sdkHandler.appId()
5059
Assert.assertNotNull(appId)
5160
}
5261

5362
@Test
5463
fun testIsAvailable_Returns_False() {
55-
every { agConnectOptionsSpy.getString(HmsConstants.APP_ID_KEY) } throws RuntimeException("Something Went Wrong")
64+
every { agConnectOptionsSpy.getString(APP_ID_KEY) } throws RuntimeException("Something Went Wrong")
5665
Assert.assertFalse(sdkHandler.isAvailable)
5766
}
5867

5968
@Test
6069
fun testIsAvailable_Returns_True() {
61-
every { agConnectOptionsSpy.getString(HmsConstants.APP_ID_KEY) } returns HmsTestConstants.HMS_APP_ID
70+
every { agConnectOptionsSpy.getString(APP_ID_KEY) } returns HmsTestConstants.HMS_APP_ID
6271
Assert.assertTrue(sdkHandler.isAvailable)
6372
}
6473

@@ -93,8 +102,8 @@ class HmsHandlerTest : BaseTestCase() {
93102

94103
@Test
95104
fun testNewToken_Exception() {
96-
every { agConnectOptionsSpy.getString(HmsConstants.APP_ID_KEY) } returns HmsTestConstants.HMS_APP_ID
97-
every { instance.getToken(HmsTestConstants.HMS_APP_ID, HmsConstants.HCM_SCOPE) } throws
105+
every { agConnectOptionsSpy.getString(APP_ID_KEY) } returns HmsTestConstants.HMS_APP_ID
106+
every { instance.getToken(HmsTestConstants.HMS_APP_ID, HCM_SCOPE) } throws
98107
RuntimeException("Something went wrong")
99108
mockkStatic(HmsInstanceId::class) {
100109
every { HmsInstanceId.getInstance(application) } returns instance
@@ -103,20 +112,201 @@ class HmsHandlerTest : BaseTestCase() {
103112
}
104113
}
105114

115+
// Add these test cases to your existing HmsHandlerTest.kt file
116+
117+
@Test
118+
fun `appId should initialize options when options is null and return app id from existing AGConnect instance`() {
119+
// Arrange
120+
val mockOptions = mockk<AGConnectOptions>()
121+
val mockInstance = mockk<AGConnectInstance>()
122+
123+
mockkStatic(AGConnectInstance::class)
124+
every { AGConnectInstance.getInstance() } returns mockInstance
125+
every { mockInstance.options } returns mockOptions
126+
every { mockOptions.getString(APP_ID_KEY) } returns "test-app-id"
127+
128+
val handler = HmsSdkHandler(application, cleverTapInstanceConfig)
129+
130+
// Act
131+
val result = handler.appId()
132+
133+
// Assert
134+
assertEquals("test-app-id", result)
135+
verify { AGConnectInstance.getInstance() }
136+
verify { mockInstance.options }
137+
}
138+
139+
@Test
140+
fun `appId should fallback to AGConnectOptionsBuilder when existing instance is null`() {
141+
// Arrange
142+
val mockOptions = mockk<AGConnectOptions>()
143+
144+
mockkStatic(AGConnectInstance::class)
145+
mockkConstructor(AGConnectOptionsBuilder::class)
146+
147+
every { AGConnectInstance.getInstance() } returns null
148+
every { anyConstructed<AGConnectOptionsBuilder>().build(application) } returns mockOptions
149+
every { mockOptions.getString(APP_ID_KEY) } returns "fallback-app-id"
150+
151+
val handler = HmsSdkHandler(application, cleverTapInstanceConfig)
152+
153+
// Act
154+
val result = handler.appId()
155+
156+
// Assert
157+
assertEquals("fallback-app-id", result)
158+
verify { AGConnectInstance.getInstance() }
159+
verify { anyConstructed<AGConnectOptionsBuilder>().build(application) }
160+
}
161+
162+
@Test
163+
fun `appId should fallback to AGConnectOptionsBuilder when existing instance options is null`() {
164+
// Arrange
165+
val mockOptions = mockk<AGConnectOptions>()
166+
val mockInstance = mockk<AGConnectInstance>()
167+
168+
mockkStatic(AGConnectInstance::class)
169+
mockkConstructor(AGConnectOptionsBuilder::class)
170+
171+
every { AGConnectInstance.getInstance() } returns mockInstance
172+
every { mockInstance.options } returns null
173+
every { anyConstructed<AGConnectOptionsBuilder>().build(application) } returns mockOptions
174+
every { mockOptions.getString(APP_ID_KEY) } returns "builder-app-id"
175+
176+
val handler = HmsSdkHandler(application, cleverTapInstanceConfig)
177+
178+
// Act
179+
val result = handler.appId()
180+
181+
// Assert
182+
assertEquals("builder-app-id", result)
183+
verify { AGConnectInstance.getInstance() }
184+
verify { mockInstance.options }
185+
verify { anyConstructed<AGConnectOptionsBuilder>().build(application) }
186+
}
187+
188+
@Test
189+
fun `appId should handle exception during AGConnect instance retrieval and fallback to builder`() {
190+
// Arrange
191+
val mockOptions = mockk<AGConnectOptions>()
192+
193+
mockkStatic(AGConnectInstance::class)
194+
mockkConstructor(AGConnectOptionsBuilder::class)
195+
196+
every { AGConnectInstance.getInstance() } throws RuntimeException("AGConnect not initialized")
197+
every { anyConstructed<AGConnectOptionsBuilder>().build(application) } returns mockOptions
198+
every { mockOptions.getString(APP_ID_KEY) } returns "exception-recovery-app-id"
199+
200+
val handler = HmsSdkHandler(application, cleverTapInstanceConfig)
201+
202+
// Act
203+
val result = handler.appId()
204+
205+
// Assert
206+
assertEquals("exception-recovery-app-id", result)
207+
}
208+
209+
@Test
210+
fun `appId should handle exception during AGConnectOptionsBuilder`() {
211+
// Arrange
212+
mockkStatic(AGConnectInstance::class)
213+
mockkConstructor(AGConnectOptionsBuilder::class)
214+
215+
every { AGConnectInstance.getInstance() } returns null
216+
every { anyConstructed<AGConnectOptionsBuilder>().build(application) } throws RuntimeException("Build failed")
217+
218+
val handler = HmsSdkHandler(application, cleverTapInstanceConfig)
219+
220+
// Act
221+
val result = handler.appId()
222+
223+
// Assert
224+
assertNull(result)
225+
}
226+
227+
@Test
228+
fun `isAvailable should trigger options initialization and return true when app id exists`() {
229+
// Arrange
230+
val mockOptions = mockk<AGConnectOptions>()
231+
val mockInstance = mockk<AGConnectInstance>()
232+
233+
mockkStatic(AGConnectInstance::class)
234+
every { AGConnectInstance.getInstance() } returns mockInstance
235+
every { mockInstance.options } returns mockOptions
236+
every { mockOptions.getString(APP_ID_KEY) } returns "test-app-id"
237+
238+
val handler = HmsSdkHandler(application, cleverTapInstanceConfig)
239+
240+
// Act
241+
val result = handler.isAvailable()
242+
243+
// Assert
244+
assertTrue(result)
245+
verify { AGConnectInstance.getInstance() } // Confirms initialization was triggered
246+
}
247+
248+
@Test
249+
fun `isAvailable should trigger options initialization and return false when app id is empty`() {
250+
// Arrange
251+
val mockOptions = mockk<AGConnectOptions>()
252+
val mockInstance = mockk<AGConnectInstance>()
253+
254+
mockkStatic(AGConnectInstance::class)
255+
every { AGConnectInstance.getInstance() } returns mockInstance
256+
every { mockInstance.options } returns mockOptions
257+
every { mockOptions.getString(APP_ID_KEY) } returns ""
258+
259+
val handler = HmsSdkHandler(application, cleverTapInstanceConfig)
260+
261+
// Act
262+
val result = handler.isAvailable()
263+
264+
// Assert
265+
assertFalse(result)
266+
verify { AGConnectInstance.getInstance() } // Confirms initialization was triggered
267+
}
268+
269+
@Test
270+
fun `onNewToken should trigger options initialization and use app id for token retrieval`() {
271+
// Arrange
272+
val mockOptions = mockk<AGConnectOptions>()
273+
val mockInstance = mockk<AGConnectInstance>()
274+
val mockHmsInstanceId = mockk<HmsInstanceId>()
275+
276+
mockkStatic(AGConnectInstance::class)
277+
mockkStatic(HmsInstanceId::class)
278+
279+
every { AGConnectInstance.getInstance() } returns mockInstance
280+
every { mockInstance.options } returns mockOptions
281+
every { mockOptions.getString(APP_ID_KEY) } returns "token-app-id"
282+
every { HmsInstanceId.getInstance(application) } returns mockHmsInstanceId
283+
every { mockHmsInstanceId.getToken("token-app-id", HCM_SCOPE) } returns "test-token"
284+
285+
val handler = HmsSdkHandler(application, cleverTapInstanceConfig)
286+
287+
// Act
288+
val result = handler.onNewToken()
289+
290+
// Assert
291+
assertEquals("test-token", result)
292+
verify { AGConnectInstance.getInstance() } // Confirms initialization was triggered
293+
verify { mockHmsInstanceId.getToken("token-app-id", HCM_SCOPE) }
294+
}
295+
106296
@Test
107297
fun testNewToken_Invalid_AppId() {
108-
every { agConnectOptionsSpy.getString(HmsConstants.APP_ID_KEY) } returns null
298+
every { agConnectOptionsSpy.getString(APP_ID_KEY) } returns null
109299
val token = sdkHandler.onNewToken()
110300
Assert.assertNull(token)
111301
}
112302

113303
@Test
114304
fun testNewToken_Success() {
115-
every { agConnectOptionsSpy.getString(HmsConstants.APP_ID_KEY) } returns HmsTestConstants.HMS_APP_ID
305+
every { agConnectOptionsSpy.getString(APP_ID_KEY) } returns HmsTestConstants.HMS_APP_ID
116306
every {
117307
instance.getToken(
118308
HmsTestConstants.HMS_APP_ID,
119-
HmsConstants.HCM_SCOPE
309+
HCM_SCOPE
120310
)
121311
} returns HmsTestConstants.HMS_TOKEN
122312
mockkStatic(HmsInstanceId::class) {

docs/CTHUAWEIPUSH.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ allprojects {
5252
* Add the following to your app’s `build.gradle` file
5353

5454
```groovy
55-
implementation "com.clevertap.android:clevertap-hms-sdk:1.5.0"
55+
implementation "com.clevertap.android:clevertap-hms-sdk:1.5.1"
5656
implementation "com.huawei.hms:push:6.11.0.300"
5757
5858
//At the bottom of the file add this

docs/CTHUAWEIPUSHCHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## CleverTap Huawei Push SDK CHANGE LOG
22

3+
### Version 1.5.1 (September 26, 2025)
4+
* Adds support for custom initialization of `AGConnect` using `agconnect-services.json` from the assets folder.
5+
36
### Version 1.5.0 (March 11, 2025)
47

58
#### Breaking API Changes

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ installreferrer = "2.2"
4848
clevertap_android_sdk = "7.5.2"
4949
clevertap_rendermax_sdk = "1.0.3"
5050
clevertap_geofence_sdk = "1.4.0"
51-
clevertap_hms_sdk = "1.5.0"
51+
clevertap_hms_sdk = "1.5.1"
5252
clevertap_push_templates_sdk = "2.1.0"
5353

5454
# Glide

0 commit comments

Comments
 (0)