Skip to content

Commit 7a2049c

Browse files
authored
Merge branch 'master' into bugfix/MOB-6575-Android-crash-on-press-push-notification
2 parents 8bbcd3f + 8802620 commit 7a2049c

File tree

16 files changed

+297
-24
lines changed

16 files changed

+297
-24
lines changed

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [3.5.10]
6+
7+
### Added
8+
- Added `mobileFrameworkInfo` configuration option to `IterableConfig` to identify the mobile framework (Flutter, React Native, or Native) being used with the SDK.
9+
- Support for push notifications with text input. Pending intent is now mutable when buttons are of text input type.
10+
11+
## [3.5.9]
12+
13+
### Fixed
14+
- Fixed notification tracking bug that prevents SDK from receiving push notifications when system notification settings are turned off.
15+
16+
## [3.5.8]
17+
18+
### Fixed
19+
- Fixed logic issue where notifications were being disabled even when auto push registration was turned off
20+
21+
## [3.5.7]
22+
23+
### Added
24+
- Support for JSON-only in-app messages, JSON-only messages are now handled by the `onNewInApp` handler and consumed after retrieval
25+
- Enhanced notification state tracking to align with system notification permissions changes
26+
527
## [3.5.6]
628

729
#### Fixed

iterableapi-ui/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ dependencies {
5151

5252
ext {
5353
libraryName = 'iterableapi-ui'
54-
libraryVersion = '3.5.6'
54+
libraryVersion = '3.5.10'
5555
}
5656

5757
if (hasProperty("mavenPublishEnabled")) {

iterableapi/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ android {
1919
minSdkVersion 16
2020
targetSdkVersion 31
2121

22-
buildConfigField "String", "ITERABLE_SDK_VERSION", "\"3.5.6\""
22+
buildConfigField "String", "ITERABLE_SDK_VERSION", "\"3.5.10\""
2323

2424
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
2525
}
@@ -85,7 +85,7 @@ dependencies {
8585

8686
ext {
8787
libraryName = 'iterableapi'
88-
libraryVersion = '3.5.6'
88+
libraryVersion = '3.5.10'
8989
}
9090

9191
if (hasProperty("mavenPublishEnabled")) {
@@ -119,4 +119,4 @@ afterEvaluate {
119119
javadoc.classpath += files(android.libraryVariants.collect { variant -> variant.javaCompile.classpath.files })
120120
javadoc.classpath += files(android.libraryVariants.collect { variant -> "build/generated/source/r/${variant.flavorName}/${variant.buildType.name}" })
121121
javadoc.classpath += files(android.libraryVariants.collect { variant -> "build/generated/source/buildConfig/${variant.flavorName}/${variant.buildType.name}" })
122-
}
122+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.iterable.iterableapi;
2+
3+
import androidx.annotation.NonNull;
4+
import androidx.annotation.Nullable;
5+
6+
public class IterableAPIMobileFrameworkInfo {
7+
@NonNull private final IterableAPIMobileFrameworkType frameworkType;
8+
@Nullable private final String iterableSdkVersion;
9+
10+
public IterableAPIMobileFrameworkInfo(@NonNull IterableAPIMobileFrameworkType frameworkType, @Nullable String iterableSdkVersion) {
11+
this.frameworkType = frameworkType;
12+
this.iterableSdkVersion = iterableSdkVersion;
13+
}
14+
15+
@NonNull
16+
public IterableAPIMobileFrameworkType getFrameworkType() {
17+
return frameworkType;
18+
}
19+
20+
@Nullable
21+
public String getIterableSdkVersion() {
22+
return iterableSdkVersion;
23+
}
24+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.iterable.iterableapi;
2+
3+
public enum IterableAPIMobileFrameworkType {
4+
FLUTTER("flutter"),
5+
REACT_NATIVE("reactnative"),
6+
NATIVE("native");
7+
8+
private final String value;
9+
10+
IterableAPIMobileFrameworkType(String value) {
11+
this.value = value;
12+
}
13+
14+
public String getValue() {
15+
return value;
16+
}
17+
}

iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -410,12 +410,8 @@ private void onForeground() {
410410
boolean isNotificationEnabled = sharedPref.getBoolean(IterableConstants.SHARED_PREFS_DEVICE_NOTIFICATIONS_ENABLED, false);
411411

412412
if (sharedInstance.isInitialized()) {
413-
if (hasStoredPermission && (isNotificationEnabled != systemNotificationEnabled)) {
414-
if (!systemNotificationEnabled) {
415-
sharedInstance.disablePush();
416-
} else {
417-
sharedInstance.registerForPush();
418-
}
413+
if (sharedInstance.config.autoPushRegistration && hasStoredPermission && (isNotificationEnabled != systemNotificationEnabled)) {
414+
sharedInstance.registerForPush();
419415
}
420416

421417
SharedPreferences.Editor editor = sharedPref.edit();
@@ -645,7 +641,7 @@ public static void initialize(@NonNull Context context, @NonNull String apiKey,
645641
try {
646642
JSONObject dataFields = new JSONObject();
647643
JSONObject deviceDetails = new JSONObject();
648-
DeviceInfoUtils.populateDeviceDetails(deviceDetails, context, sharedInstance.getDeviceId());
644+
DeviceInfoUtils.populateDeviceDetails(deviceDetails, context, sharedInstance.getDeviceId(), null);
649645
dataFields.put(IterableConstants.KEY_FIRETV, deviceDetails);
650646
sharedInstance.apiClient.updateUser(dataFields, false);
651647
} catch (JSONException e) {

iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,21 @@ protected void registerDeviceToken(@Nullable String email, @Nullable String user
556556

557557
dataFields.put(IterableConstants.FIREBASE_TOKEN_TYPE, IterableConstants.MESSAGING_PLATFORM_FIREBASE);
558558
dataFields.put(IterableConstants.FIREBASE_COMPATIBLE, true);
559-
DeviceInfoUtils.populateDeviceDetails(dataFields, context, authProvider.getDeviceId());
559+
560+
IterableAPIMobileFrameworkInfo frameworkInfo = IterableApi.sharedInstance.config.mobileFrameworkInfo;
561+
if (frameworkInfo == null) {
562+
IterableAPIMobileFrameworkType detectedFramework = IterableMobileFrameworkDetector.detectFramework(context);
563+
String sdkVersion = detectedFramework == IterableAPIMobileFrameworkType.NATIVE
564+
? IterableConstants.ITBL_KEY_SDK_VERSION_NUMBER
565+
: null;
566+
567+
frameworkInfo = new IterableAPIMobileFrameworkInfo(
568+
detectedFramework,
569+
sdkVersion
570+
);
571+
}
572+
573+
DeviceInfoUtils.populateDeviceDetails(dataFields, context, authProvider.getDeviceId(), frameworkInfo);
560574
dataFields.put(IterableConstants.DEVICE_NOTIFICATIONS_ENABLED, NotificationManagerCompat.from(context).areNotificationsEnabled());
561575

562576
JSONObject device = new JSONObject();
@@ -684,4 +698,4 @@ void onLogout() {
684698
getRequestProcessor().onLogout(authProvider.getContext());
685699
authProvider.resetAuth();
686700
}
687-
}
701+
}

iterableapi/src/main/java/com/iterable/iterableapi/IterableConfig.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.iterable.iterableapi;
22

33
import androidx.annotation.NonNull;
4+
import androidx.annotation.Nullable;
45
import android.util.Log;
56

67
/**
@@ -86,6 +87,7 @@ public class IterableConfig {
8687
* By default, the SDK will save in-apps to disk.
8788
*/
8889
final boolean useInMemoryStorageForInApps;
90+
8991
/**
9092
* Allows for fetching embedded messages.
9193
*/
@@ -97,6 +99,12 @@ public class IterableConfig {
9799
*/
98100
final IterableDecryptionFailureHandler decryptionFailureHandler;
99101

102+
/**
103+
* Mobile framework information for the app
104+
*/
105+
@Nullable
106+
final IterableAPIMobileFrameworkInfo mobileFrameworkInfo;
107+
100108
private IterableConfig(Builder builder) {
101109
pushIntegrationName = builder.pushIntegrationName;
102110
urlHandler = builder.urlHandler;
@@ -114,6 +122,7 @@ private IterableConfig(Builder builder) {
114122
useInMemoryStorageForInApps = builder.useInMemoryStorageForInApps;
115123
enableEmbeddedMessaging = builder.enableEmbeddedMessaging;
116124
decryptionFailureHandler = builder.decryptionFailureHandler;
125+
mobileFrameworkInfo = builder.mobileFrameworkInfo;
117126
}
118127

119128
public static class Builder {
@@ -133,6 +142,7 @@ public static class Builder {
133142
private boolean useInMemoryStorageForInApps = false;
134143
private boolean enableEmbeddedMessaging = false;
135144
private IterableDecryptionFailureHandler decryptionFailureHandler;
145+
private IterableAPIMobileFrameworkInfo mobileFrameworkInfo;
136146

137147
public Builder() {}
138148

@@ -304,6 +314,16 @@ public Builder setDecryptionFailureHandler(@NonNull IterableDecryptionFailureHan
304314
return this;
305315
}
306316

317+
/**
318+
* Set mobile framework information for the app
319+
* @param mobileFrameworkInfo Mobile framework information
320+
*/
321+
@NonNull
322+
public Builder setMobileFrameworkInfo(@NonNull IterableAPIMobileFrameworkInfo mobileFrameworkInfo) {
323+
this.mobileFrameworkInfo = mobileFrameworkInfo;
324+
return this;
325+
}
326+
307327
@NonNull
308328
public IterableConfig build() {
309329
return new IterableConfig(this);

iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ public final class IterableConstants {
144144
public static final String DEVICE_APP_BUILD = "appBuild";
145145
public static final String DEVICE_NOTIFICATIONS_ENABLED = "notificationsEnabled";
146146
public static final String DEVICE_ITERABLE_SDK_VERSION = "iterableSdkVersion";
147+
public static final String DEVICE_MOBILE_FRAMEWORK_INFO = "mobileFrameworkInfo";
148+
public static final String DEVICE_FRAMEWORK_TYPE = "frameworkType";
147149

148150
public static final String INSTANCE_ID_CLASS = "com.google.android.gms.iid.InstanceID";
149151
public static final String ICON_FOLDER_IDENTIFIER = "drawable";
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.iterable.iterableapi
2+
3+
import android.content.Context
4+
import android.content.pm.PackageManager
5+
6+
object IterableMobileFrameworkDetector {
7+
private const val TAG = "FrameworkDetector"
8+
private lateinit var context: Context
9+
10+
@Volatile
11+
private var cachedFrameworkType: IterableAPIMobileFrameworkType? = null
12+
13+
private var hasClass: (String) -> Boolean = { className ->
14+
try {
15+
Class.forName(className)
16+
true
17+
} catch (e: ClassNotFoundException) {
18+
false
19+
}
20+
}
21+
22+
fun initialize(context: Context) {
23+
if (context.applicationContext != null) {
24+
this.context = context.applicationContext
25+
} else {
26+
this.context = context
27+
}
28+
if (cachedFrameworkType == null) {
29+
cachedFrameworkType = detectFrameworkInternal(context)
30+
}
31+
}
32+
33+
@JvmStatic
34+
fun detectFramework(context: Context): IterableAPIMobileFrameworkType {
35+
return cachedFrameworkType ?: synchronized(this) {
36+
cachedFrameworkType ?: detectFrameworkInternal(context).also {
37+
cachedFrameworkType = it
38+
}
39+
}
40+
}
41+
42+
fun frameworkType(): IterableAPIMobileFrameworkType {
43+
return cachedFrameworkType ?: detectFramework(context)
44+
}
45+
46+
private fun detectFrameworkInternal(context: Context): IterableAPIMobileFrameworkType {
47+
val hasFlutter = hasFrameworkClasses(FrameworkClasses.flutter)
48+
val hasReactNative = hasFrameworkClasses(FrameworkClasses.reactNative)
49+
50+
return when {
51+
hasFlutter && hasReactNative -> {
52+
IterableLogger.d(TAG, "Both Flutter and React Native frameworks detected. This is unexpected.")
53+
when {
54+
context.packageName.endsWith(".flutter") -> IterableAPIMobileFrameworkType.FLUTTER
55+
hasManifestMetadata(context, ManifestMetadata.flutter) -> IterableAPIMobileFrameworkType.FLUTTER
56+
hasManifestMetadata(context, ManifestMetadata.reactNative) -> IterableAPIMobileFrameworkType.REACT_NATIVE
57+
else -> IterableAPIMobileFrameworkType.REACT_NATIVE
58+
}
59+
}
60+
hasFlutter -> IterableAPIMobileFrameworkType.FLUTTER
61+
hasReactNative -> IterableAPIMobileFrameworkType.REACT_NATIVE
62+
else -> {
63+
when {
64+
hasManifestMetadata(context, ManifestMetadata.flutter) -> IterableAPIMobileFrameworkType.FLUTTER
65+
hasManifestMetadata(context, ManifestMetadata.reactNative) -> IterableAPIMobileFrameworkType.REACT_NATIVE
66+
else -> IterableAPIMobileFrameworkType.NATIVE
67+
}
68+
}
69+
}
70+
}
71+
72+
private object FrameworkClasses {
73+
val flutter = listOf(
74+
"io.flutter.embedding.engine.FlutterEngine",
75+
"io.flutter.plugin.common.MethodChannel",
76+
"io.flutter.embedding.android.FlutterActivity",
77+
"io.flutter.embedding.android.FlutterFragment"
78+
)
79+
80+
val reactNative = listOf(
81+
"com.facebook.react.ReactRootView",
82+
"com.facebook.react.bridge.ReactApplicationContext",
83+
"com.facebook.react.ReactActivity",
84+
"com.facebook.react.ReactApplication",
85+
"com.facebook.react.bridge.ReactContext"
86+
)
87+
}
88+
89+
private object ManifestMetadata {
90+
val flutter = listOf(
91+
"flutterEmbedding",
92+
"io.flutter.embedding.android.NormalTheme",
93+
"io.flutter.embedding.android.SplashScreenDrawable"
94+
)
95+
96+
val reactNative = listOf(
97+
"react_native_version",
98+
"expo.modules.updates.ENABLED",
99+
"com.facebook.react.selected.ReactActivity"
100+
)
101+
}
102+
103+
private fun hasFrameworkClasses(classNames: List<String>): Boolean {
104+
return classNames.any { hasClass(it) }
105+
}
106+
107+
private fun hasManifestMetadata(context: Context, metadataKeys: List<String>): Boolean {
108+
return try {
109+
val packageInfo = context.packageManager.getPackageInfo(
110+
context.packageName,
111+
PackageManager.GET_META_DATA
112+
)
113+
val metadata = packageInfo.applicationInfo.metaData
114+
metadataKeys.any { key -> metadata?.containsKey(key) == true }
115+
} catch (e: Exception) {
116+
IterableLogger.e(TAG, "Error checking manifest metadata: ${e.message}")
117+
false
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)