diff --git a/CHANGELOG.md b/CHANGELOG.md
index d3cba33c0d..be56593893 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,21 @@
+## 32.1.0
+
+[Release Date](https://github.com/braze-inc/braze-android-sdk/releases/tag/v32.1.0)
+
+##### Fixed
+- Fixed an issue where geofence events could not be sent when the app is in the background.
+- Fixed an issue where In-App Messages would fail to be dismissed when the host app is using the predictive back gesture.
+
+##### Added
+- Added support for an upcoming Braze SDK Debugging tool.
+- Added the ability to prevent certain edge cases where the SDK could show In-App Messages to different users than the one that triggered the In-App Message.
+ - Configured via `braze.xml` through `true`.
+ - Can also be configured via runtime configuration through `BrazeConfig.setShouldPreventInAppMessageDisplayForDifferentUsers()`.
+ - Defaults to false. Note that even when false, the SDK will still prevent most cases of showing In-App Messages to different users. This configuration option is designed to prevent edge cases such as when the user changes while on a `BrazeActivityLifecycleCallbackListener` blocked Activity or when a mismatched message is still in the stack.
+
+##### Changed
+- Changed the behavior of the `Braze.getDeviceId()` method to return a different device ID based on the API key used to initialize the SDK.
+
## 32.0.0
[Release Date](https://github.com/braze-inc/braze-android-sdk/releases/tag/v32.0.0)
diff --git a/README.md b/README.md
index bcfd8dc2ca..1045fcc90d 100644
--- a/README.md
+++ b/README.md
@@ -36,8 +36,8 @@ Our SDK is now hosted in Maven Central. You can remove `https://braze-inc.github
```
dependencies {
- implementation 'com.braze:android-sdk-ui:32.0.+'
- implementation 'com.braze:android-sdk-location:32.0.+'
+ implementation 'com.braze:android-sdk-ui:32.1.+'
+ implementation 'com.braze:android-sdk-location:32.1.+'
...
}
```
@@ -56,7 +56,7 @@ repositories {
```
dependencies {
- implementation 'com.braze:android-sdk-ui:32.0.+'
+ implementation 'com.braze:android-sdk-ui:32.1.+'
}
```
diff --git a/android-sdk-location/src/main/AndroidManifest.xml b/android-sdk-location/src/main/AndroidManifest.xml
index 1759c1a896..dbfeac8e2f 100644
--- a/android-sdk-location/src/main/AndroidManifest.xml
+++ b/android-sdk-location/src/main/AndroidManifest.xml
@@ -3,7 +3,7 @@
+ android:exported="true">
diff --git a/android-sdk-location/src/main/java/com/braze/location/GooglePlayLocationUtils.kt b/android-sdk-location/src/main/java/com/braze/location/GooglePlayLocationUtils.kt
index a94b66ee7b..416ae83549 100644
--- a/android-sdk-location/src/main/java/com/braze/location/GooglePlayLocationUtils.kt
+++ b/android-sdk-location/src/main/java/com/braze/location/GooglePlayLocationUtils.kt
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.content.SharedPreferences
+import androidx.annotation.VisibleForTesting
import com.braze.managers.BrazeGeofenceManager
import com.braze.managers.IBrazeGeofenceLocationUpdateListener
import com.braze.models.BrazeGeofence
@@ -28,44 +29,63 @@ object GooglePlayLocationUtils {
*
* If a given geofence is already registered with Google Play Location Services, it will not be
* needlessly re-registered. Geofences that are registered with Google Play Location Services but
- * not included in the given list of geofences will be un-registered.
+ * not included in [desiredGeofencesToRegister] will be un-registered.
*
- * If the given geofence list is empty, geofences will be un-registered and deleted from local
+ * If [desiredGeofencesToRegister] is empty, all geofences will be un-registered and deleted from local
* storage.
* @param context used by shared preferences
- * @param geofenceList list of [BrazeGeofence] objects
- * @param geofenceRequestIntent pending intent to fire when geofences transition events occur
+ * @param desiredGeofencesToRegister list of [BrazeGeofence] objects to register if new or updated. Otherwise ignored.
+ * @param geofenceRequestIntent pending intent to fire when geofences transition events occur.
+ * @param removalFunction function to remove geofences from Google Play Location Services.
+ * @param registerFunction function to register geofences with Google Play Location Services.
*/
@JvmStatic
fun registerGeofencesWithGooglePlayIfNecessary(
context: Context,
- geofenceList: List,
- geofenceRequestIntent: PendingIntent
+ desiredGeofencesToRegister: List,
+ geofenceRequestIntent: PendingIntent,
+ removalFunction: (List) -> Unit = { removeGeofencesRegisteredWithGeofencingClient(context, it) },
+ registerFunction: (List) -> Unit = { registerGeofencesWithGeofencingClient(context, it, geofenceRequestIntent) }
) {
+ brazelog(V) { "registerGeofencesWithGooglePlayIfNecessary called with $desiredGeofencesToRegister" }
try {
val prefs = getRegisteredGeofenceSharedPrefs(context)
val registeredGeofences = BrazeGeofenceManager.retrieveBrazeGeofencesFromLocalStorage(prefs)
+ val registeredGeofencesById = registeredGeofences.associateBy { it.id }
- val newGeofencesToRegister = geofenceList.filter { newGeofence ->
- registeredGeofences.none { registeredGeofence ->
- registeredGeofence.id == newGeofence.id && registeredGeofence.equivalentServerData(newGeofence)
- }
- }
+ // Given the input [desiredGeofencesToRegister] and the [registeredGeofences], we need to determine
+ // which geofences are to be registered, which are obsolete, and which will be no-ops.
+ // We can do this by comparing the geofence data against the registered geofences.
+ // We only want to register geofences that are not already registered.
+ // An obsolete Geofence is one that is registered with Google Play Services but is not in the desired list.
+
+ // If any previously registered Geofence is missing from the desired list, it is obsolete.
val obsoleteGeofenceIds = registeredGeofences.filter { registeredGeofence ->
- newGeofencesToRegister.none { newGeofence ->
- newGeofence.id == registeredGeofence.id
+ desiredGeofencesToRegister.none { desiredGeofence ->
+ desiredGeofence.id == registeredGeofence.id
}
}.map { it.id }
+ // If any desired Geofence is not already registered, it is new and needs to be registered.
+ // Additionally, any previously registered geofence that has received updates should be re-registered.
+ val newGeofencesToRegister = mutableListOf()
+ for (desiredGeofence in desiredGeofencesToRegister) {
+ val registeredGeofenceWithSameId = registeredGeofencesById[desiredGeofence.id]
+ if (registeredGeofenceWithSameId == null || !desiredGeofence.equivalentServerData(registeredGeofenceWithSameId)) {
+ brazelog { "Geofence with id: ${desiredGeofence.id} is new or has been updated." }
+ newGeofencesToRegister.add(desiredGeofence)
+ }
+ }
+
if (obsoleteGeofenceIds.isNotEmpty()) {
- brazelog { "Un-registering ${obsoleteGeofenceIds.size} obsolete geofences from Google Play Services." }
- removeGeofencesRegisteredWithGeofencingClient(context, obsoleteGeofenceIds)
+ brazelog { "Un-registering $obsoleteGeofenceIds obsolete geofences from Google Play Services." }
+ removalFunction(obsoleteGeofenceIds)
} else {
brazelog { "No obsolete geofences need to be unregistered from Google Play Services." }
}
if (newGeofencesToRegister.isNotEmpty()) {
- brazelog { "Registering ${newGeofencesToRegister.size} new geofences with Google Play Services." }
- registerGeofencesWithGeofencingClient(context, newGeofencesToRegister, geofenceRequestIntent)
+ brazelog { "Registering $newGeofencesToRegister new geofences with Google Play Services." }
+ registerFunction(newGeofencesToRegister)
} else {
brazelog { "No new geofences need to be registered with Google Play Services." }
}
@@ -166,7 +186,8 @@ object GooglePlayLocationUtils {
*
* @param obsoleteGeofenceIds List of [String]s containing Geofence IDs that needs to be un-registered
*/
- private fun removeGeofencesRegisteredWithGeofencingClient(context: Context, obsoleteGeofenceIds: List) {
+ @VisibleForTesting
+ internal fun removeGeofencesRegisteredWithGeofencingClient(context: Context, obsoleteGeofenceIds: List) {
LocationServices.getGeofencingClient(context).removeGeofences(obsoleteGeofenceIds)
.addOnSuccessListener {
brazelog { "Geofences successfully un-registered with Google Play Services." }
@@ -201,7 +222,8 @@ object GooglePlayLocationUtils {
/**
* Returns a [SharedPreferences] instance holding list of registered [BrazeGeofence]s.
*/
- private fun getRegisteredGeofenceSharedPrefs(context: Context): SharedPreferences =
+ @VisibleForTesting
+ internal fun getRegisteredGeofenceSharedPrefs(context: Context): SharedPreferences =
context.getSharedPreferences(REGISTERED_GEOFENCE_SHARED_PREFS_LOCATION, Context.MODE_PRIVATE)
/**
@@ -209,7 +231,8 @@ object GooglePlayLocationUtils {
*
* @param newGeofencesToRegister List of [BrazeGeofence]s to store in SharedPreferences
*/
- private fun storeGeofencesToSharedPrefs(context: Context, newGeofencesToRegister: List) {
+ @VisibleForTesting
+ internal fun storeGeofencesToSharedPrefs(context: Context, newGeofencesToRegister: List) {
val editor = getRegisteredGeofenceSharedPrefs(context).edit()
for (brazeGeofence in newGeofencesToRegister) {
editor.putString(brazeGeofence.id, brazeGeofence.forJsonPut().toString())
diff --git a/android-sdk-ui/src/main/java/com/braze/BrazeActivityLifecycleCallbackListener.kt b/android-sdk-ui/src/main/java/com/braze/BrazeActivityLifecycleCallbackListener.kt
index 0934e04859..8111206eca 100644
--- a/android-sdk-ui/src/main/java/com/braze/BrazeActivityLifecycleCallbackListener.kt
+++ b/android-sdk-ui/src/main/java/com/braze/BrazeActivityLifecycleCallbackListener.kt
@@ -124,15 +124,10 @@ open class BrazeActivityLifecycleCallbackListener @JvmOverloads constructor(
}
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
- if (registerInAppMessageManager &&
- shouldHandleLifecycleMethodsInActivity(activity, false)
- ) {
- brazelog(V) {
- "Automatically calling lifecycle method: ensureSubscribedToInAppMessageEvents for class: ${activity.javaClass}"
- }
- BrazeInAppMessageManager.getInstance()
- .ensureSubscribedToInAppMessageEvents(activity.applicationContext)
+ brazelog(V) {
+ "Automatically calling lifecycle method: ensureSubscribedToInAppMessageEvents for class: ${activity.javaClass}"
}
+ BrazeInAppMessageManager.getInstance().ensureSubscribedToInAppMessageEvents(activity.applicationContext)
}
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
diff --git a/android-sdk-ui/src/main/java/com/braze/ui/inappmessage/BrazeInAppMessageManager.kt b/android-sdk-ui/src/main/java/com/braze/ui/inappmessage/BrazeInAppMessageManager.kt
index e34e1b8533..d7182d2c4b 100644
--- a/android-sdk-ui/src/main/java/com/braze/ui/inappmessage/BrazeInAppMessageManager.kt
+++ b/android-sdk-ui/src/main/java/com/braze/ui/inappmessage/BrazeInAppMessageManager.kt
@@ -10,6 +10,7 @@ import com.braze.BrazeInternal
import com.braze.BrazeInternal.retryInAppMessage
import com.braze.configuration.BrazeConfigurationProvider
import com.braze.enums.inappmessage.Orientation
+import com.braze.events.BrazeUserChangeEvent
import com.braze.events.IEventSubscriber
import com.braze.events.InAppMessageEvent
import com.braze.events.SdkDataWipeEvent
@@ -78,6 +79,7 @@ import kotlin.concurrent.withLock
*/
// Static field leak doesn't apply to this singleton since the activity is nullified after the manager is unregistered.
@SuppressLint("StaticFieldLeak")
+@Suppress("TooManyFunctions")
open class BrazeInAppMessageManager : InAppMessageManagerBase() {
private val inAppMessageViewLifecycleListener: IInAppMessageViewLifecycleListener =
DefaultInAppMessageViewLifecycleListener()
@@ -94,10 +96,16 @@ open class BrazeInAppMessageManager : InAppMessageManagerBase() {
val inAppMessageEventMap = mutableMapOf()
private var inAppMessageEventSubscriber: IEventSubscriber? = null
private var sdkDataWipeEventSubscriber: IEventSubscriber? = null
+ private var brazeUserChangeEventSubscriber: IEventSubscriber? = null
private var originalOrientation: Int? = null
private var configurationProvider: BrazeConfigurationProvider? = null
private var inAppMessageViewWrapper: IInAppMessageViewWrapper? = null
+ /**
+ * The last seen user id from the [BrazeUserChangeEvent] subscriber.
+ */
+ private var currentUserId: String = ""
+
/**
* An In-App Message being carried over during the
* [unregisterInAppMessageManager]
@@ -167,6 +175,20 @@ open class BrazeInAppMessageManager : InAppMessageManagerBase() {
it, SdkDataWipeEvent::class.java
)
}
+
+ if (brazeUserChangeEventSubscriber != null) {
+ brazelog(V) { "Removing existing user change event subscriber before subscribing a new one." }
+ getInstance(context).removeSingleSubscription(
+ brazeUserChangeEventSubscriber,
+ BrazeUserChangeEvent::class.java
+ )
+ }
+
+ brazeUserChangeEventSubscriber = createBrazeUserChangeEventSubscriber(context).also {
+ getInstance(context).addSingleSynchronousSubscription(
+ it, BrazeUserChangeEvent::class.java
+ )
+ }
}
/**
@@ -485,6 +507,19 @@ open class BrazeInAppMessageManager : InAppMessageManagerBase() {
throw Exception("Current orientation did not match specified orientation for in-app message. Doing nothing.")
}
+ // Verify this message is for the intended user
+ val configProvider = configurationProvider
+ ?: throw Exception(
+ "configurationProvider is null. The in-app message will not be displayed and will not be" +
+ "put back on the stack."
+ )
+ if (configProvider.isPreventInAppMessageDisplayForDifferentUsersEnabled && !isInAppMessageForTheSameUser(inAppMessage, currentUserId)) {
+ throw Exception(
+ "The last identifier user $currentUserId does not match the in-app message's user. " +
+ "The in-app message will not be displayed and will not be put back on the stack."
+ )
+ }
+
// At this point, the only factors that would inhibit in-app message display are view creation issues.
// Since control in-app messages have no view, this is the end of execution for control in-app messages
if (inAppMessage.isControl) {
@@ -520,10 +555,7 @@ open class BrazeInAppMessageManager : InAppMessageManagerBase() {
resetAfterInAppMessageClose()
return
}
- val inAppMessageViewFactory = getInAppMessageViewFactory(inAppMessage)
- if (inAppMessageViewFactory == null) {
- throw Exception("ViewFactory from getInAppMessageViewFactory was null.")
- }
+ val inAppMessageViewFactory = getInAppMessageViewFactory(inAppMessage) ?: throw Exception("ViewFactory from getInAppMessageViewFactory was null.")
val inAppMessageView = inAppMessageViewFactory.createInAppMessageView(
activity, inAppMessage
)
@@ -542,11 +574,6 @@ open class BrazeInAppMessageManager : InAppMessageManagerBase() {
)
}
- val configProvider = configurationProvider
- ?: throw Exception(
- "configurationProvider is null. The in-app message will not be displayed and will not be" +
- "put back on the stack."
- )
val openingAnimation = inAppMessageAnimationFactory.getOpeningAnimation(inAppMessage)
val closingAnimation = inAppMessageAnimationFactory.getClosingAnimation(inAppMessage)
val viewWrapperFactory = inAppMessageViewWrapperFactory
@@ -635,6 +662,24 @@ open class BrazeInAppMessageManager : InAppMessageManagerBase() {
}
}
+ private fun createBrazeUserChangeEventSubscriber(context: Context): IEventSubscriber {
+ return IEventSubscriber { event: BrazeUserChangeEvent ->
+ brazelog(V) { "InAppMessage manager handling new current user id: '$event'" }
+ val configurationProvider = BrazeInternal.getConfigurationProvider(context)
+ if (!configurationProvider.isPreventInAppMessageDisplayForDifferentUsersEnabled) {
+ brazelog(V) { "Not cleansing in-app messages on user id change" }
+ return@IEventSubscriber
+ }
+ val currentUserId = event.currentUserId
+ this.currentUserId = currentUserId
+ brazelog { "Removing in-app messages not from user $currentUserId" }
+
+ inAppMessageStack.removeAll { !isInAppMessageForTheSameUser(it, currentUserId) }
+ if (!isInAppMessageForTheSameUser(carryoverInAppMessage, currentUserId)) carryoverInAppMessage = null
+ if (!isInAppMessageForTheSameUser(unregisteredInAppMessage, currentUserId)) unregisteredInAppMessage = null
+ }
+ }
+
/**
* For in-app messages that have a preferred orientation, locks the screen orientation and
* returns true if the screen is currently in the preferred orientation. If the screen is not
@@ -673,6 +718,19 @@ open class BrazeInAppMessageManager : InAppMessageManagerBase() {
return true
}
+ /**
+ * Determines whether the in-app message was triggered for the same user as the current user.
+ * @return true if the in-app message was triggered for the same user as the current
+ * user, false otherwise. Returns false if the message is null.
+ */
+ @VisibleForTesting
+ open fun isInAppMessageForTheSameUser(inAppMessage: IInAppMessage?, currentUserId: String): Boolean {
+ if (inAppMessage == null) return true
+
+ val inAppMessageUserId = inAppMessageEventMap[inAppMessage]?.userId
+ return inAppMessageUserId == null || inAppMessageUserId == currentUserId
+ }
+
companion object {
private val instanceLock = ReentrantLock()
diff --git a/android-sdk-ui/src/main/java/com/braze/ui/inappmessage/DefaultInAppMessageViewWrapper.kt b/android-sdk-ui/src/main/java/com/braze/ui/inappmessage/DefaultInAppMessageViewWrapper.kt
index 40a215287d..b040071acd 100644
--- a/android-sdk-ui/src/main/java/com/braze/ui/inappmessage/DefaultInAppMessageViewWrapper.kt
+++ b/android-sdk-ui/src/main/java/com/braze/ui/inappmessage/DefaultInAppMessageViewWrapper.kt
@@ -1,11 +1,14 @@
package com.braze.ui.inappmessage
import android.app.Activity
+import android.os.Build
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.widget.FrameLayout
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.braze.configuration.BrazeConfigurationProvider
@@ -23,6 +26,7 @@ import com.braze.ui.inappmessage.listeners.IInAppMessageViewLifecycleListener
import com.braze.ui.inappmessage.listeners.SwipeDismissTouchListener.DismissCallbacks
import com.braze.ui.inappmessage.listeners.TouchAwareSwipeDismissTouchListener
import com.braze.ui.inappmessage.listeners.TouchAwareSwipeDismissTouchListener.ITouchListener
+import com.braze.ui.inappmessage.utils.InAppMessageViewUtils
import com.braze.ui.inappmessage.views.IInAppMessageImmersiveView
import com.braze.ui.inappmessage.views.IInAppMessageView
import com.braze.ui.inappmessage.views.InAppMessageHtmlBaseView
@@ -154,6 +158,19 @@ open class DefaultInAppMessageViewWrapper @JvmOverloads constructor(
inAppMessageViewLifecycleListener
)
}
+
+ if (BrazeInAppMessageManager.getInstance().doesBackButtonDismissInAppMessageView && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ activity.let {
+ val dismissInAppMessageCallback = object : OnBackInvokedCallback {
+ override fun onBackInvoked() {
+ InAppMessageViewUtils.closeInAppMessageOnKeycodeBack()
+ it.onBackInvokedDispatcher.unregisterOnBackInvokedCallback(this)
+ }
+ }
+
+ it.onBackInvokedDispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_OVERLAY, dismissInAppMessageCallback)
+ }
+ }
}
override fun close() {
diff --git a/build.gradle b/build.gradle
index 63bbb36206..5580b178d7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -21,7 +21,7 @@ ext {
compileSdkVersion = 35
minSdkVersion = 21
targetSdkVersion = 35
- appVersionName = '32.0.0'
+ appVersionName = '32.1.0'
}
subprojects {
@@ -33,5 +33,5 @@ subprojects {
}
group = 'com.braze'
- version = '32.0.0'
+ version = '32.1.0'
}
diff --git a/droidboy/src/main/java/com/appboy/sample/activity/GeofencesMapActivity.java b/droidboy/src/main/java/com/appboy/sample/activity/GeofencesMapActivity.java
deleted file mode 100644
index 98cf7272fc..0000000000
--- a/droidboy/src/main/java/com/appboy/sample/activity/GeofencesMapActivity.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.appboy.sample.activity;
-
-import static com.appboy.sample.R.id.map;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.graphics.Color;
-import android.os.Bundle;
-
-import androidx.appcompat.app.AppCompatActivity;
-
-import com.appboy.sample.R;
-import com.braze.models.BrazeGeofence;
-import com.braze.support.BrazeLogger;
-import com.braze.support.StringUtils;
-import com.google.android.gms.maps.CameraUpdateFactory;
-import com.google.android.gms.maps.GoogleMap;
-import com.google.android.gms.maps.OnMapReadyCallback;
-import com.google.android.gms.maps.SupportMapFragment;
-import com.google.android.gms.maps.model.CircleOptions;
-import com.google.android.gms.maps.model.LatLng;
-import com.google.android.gms.maps.model.MarkerOptions;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class GeofencesMapActivity extends AppCompatActivity implements OnMapReadyCallback {
- private static final String TAG = BrazeLogger.getBrazeLogTag(GeofencesMapActivity.class);
- private static final String REGISTERED_GEOFENCE_SHARED_PREFS_LOCATION = "com.appboy.support.geofences";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.geofences_map);
- SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
- .findFragmentById(map);
- mapFragment.getMapAsync(this);
- }
-
- @Override
- public void onMapReady(GoogleMap googleMap) {
-
- // Note that this is for testing purposes only. This storage location and format are not a supported API.
- SharedPreferences registeredGeofencePrefs = getApplicationContext()
- .getSharedPreferences(REGISTERED_GEOFENCE_SHARED_PREFS_LOCATION, Context.MODE_PRIVATE);
- List registeredGeofences = retrieveBrazeGeofencesFromLocalStorage(registeredGeofencePrefs);
-
- int color = Color.BLUE;
- if (registeredGeofences.size() > 0) {
- for (BrazeGeofence registeredGeofence : registeredGeofences) {
- googleMap.addCircle(new CircleOptions()
- .center(new LatLng(registeredGeofence.getLatitude(), registeredGeofence.getLongitude()))
- .radius(registeredGeofence.getRadiusMeters())
- .strokeColor(Color.RED)
- .fillColor(Color.argb((int) Math.round(Color.alpha(color) * .20), Color.red(color), Color.green(color), Color.blue(color))));
- googleMap.addMarker(new MarkerOptions()
- .position(new LatLng(registeredGeofence.getLatitude(), registeredGeofence.getLongitude()))
- .title("Braze Geofence")
- .snippet(registeredGeofence.getLatitude() + ", " + registeredGeofence.getLongitude()
- + ", radius: " + registeredGeofence.getRadiusMeters() + "m"));
- }
-
- BrazeGeofence firstGeofence = registeredGeofences.get(0);
- googleMap.moveCamera(CameraUpdateFactory.newLatLng(new LatLng(firstGeofence.getLatitude(), firstGeofence.getLongitude())));
- googleMap.animateCamera(CameraUpdateFactory.zoomTo(10), null);
- }
- }
-
- // Note that this is for testing purposes only. This storage location and format are not a supported API.
- private static List retrieveBrazeGeofencesFromLocalStorage(SharedPreferences sharedPreferences) {
- List geofences = new ArrayList<>();
- Map storedGeofences = sharedPreferences.getAll();
- if (storedGeofences == null || storedGeofences.size() == 0) {
- BrazeLogger.d(TAG, "Did not find stored geofences.");
- return geofences;
- }
- Set keys = storedGeofences.keySet();
- for (String key : keys) {
- String geofenceString = sharedPreferences.getString(key, null);
- try {
- if (StringUtils.isNullOrBlank(geofenceString)) {
- BrazeLogger.w(TAG, String.format("Received null or blank serialized "
- + " geofence string for geofence id %s from shared preferences. Not parsing.", key));
- continue;
- }
- JSONObject geofenceJson = new JSONObject(geofenceString);
- BrazeGeofence brazeGeofence = new BrazeGeofence(geofenceJson);
- geofences.add(brazeGeofence);
- } catch (JSONException e) {
- BrazeLogger.e(TAG, "Encountered Json exception while parsing stored geofence: " + geofenceString, e);
- } catch (Exception e) {
- BrazeLogger.e(TAG, "Encountered unexpected exception while parsing stored geofence: " + geofenceString, e);
- }
- }
- return geofences;
- }
-}
diff --git a/droidboy/src/main/java/com/appboy/sample/activity/GeofencesMapActivity.kt b/droidboy/src/main/java/com/appboy/sample/activity/GeofencesMapActivity.kt
new file mode 100644
index 0000000000..16dd303de2
--- /dev/null
+++ b/droidboy/src/main/java/com/appboy/sample/activity/GeofencesMapActivity.kt
@@ -0,0 +1,99 @@
+package com.appboy.sample.activity
+
+import android.content.SharedPreferences
+import android.graphics.Color
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.appboy.sample.R
+import com.appboy.sample.R.id
+import com.braze.models.BrazeGeofence
+import com.braze.support.BrazeLogger.Priority.E
+import com.braze.support.BrazeLogger.brazelog
+import com.braze.support.BrazeLogger.getBrazeLogTag
+import com.google.android.gms.maps.CameraUpdateFactory
+import com.google.android.gms.maps.GoogleMap
+import com.google.android.gms.maps.OnMapReadyCallback
+import com.google.android.gms.maps.SupportMapFragment
+import com.google.android.gms.maps.model.CameraPosition
+import com.google.android.gms.maps.model.CircleOptions
+import com.google.android.gms.maps.model.LatLng
+import com.google.android.gms.maps.model.MarkerOptions
+import org.json.JSONException
+import org.json.JSONObject
+
+class GeofencesMapActivity : AppCompatActivity(), OnMapReadyCallback {
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.geofences_map)
+ val mapFragment = supportFragmentManager
+ .findFragmentById(id.map) as SupportMapFragment?
+ mapFragment?.getMapAsync(this)
+ }
+
+ override fun onMapReady(googleMap: GoogleMap) {
+ // Note that this is for testing purposes only. This storage location and format are not a supported API.
+ val registeredGeofencePrefs = applicationContext
+ .getSharedPreferences(REGISTERED_GEOFENCE_SHARED_PREFS_LOCATION, MODE_PRIVATE)
+ val registeredGeofences = retrieveBrazeGeofencesFromLocalStorage(registeredGeofencePrefs)
+ val color = Color.BLUE
+
+ val cameraPosition = CameraPosition.builder()
+ .zoom(12f)
+
+ if (registeredGeofences.isNotEmpty()) {
+ for (registeredGeofence in registeredGeofences) {
+ googleMap.addCircle(
+ CircleOptions()
+ .center(LatLng(registeredGeofence.latitude, registeredGeofence.longitude))
+ .radius(registeredGeofence.radiusMeters)
+ .strokeColor(Color.RED)
+ .fillColor(Color.argb(Math.round(Color.alpha(color) * .20).toInt(), Color.red(color), Color.green(color), Color.blue(color)))
+ )
+ googleMap.addMarker(
+ MarkerOptions()
+ .position(LatLng(registeredGeofence.latitude, registeredGeofence.longitude))
+ .title("Braze Geofence")
+ .snippet(
+ registeredGeofence.latitude.toString() + ", " + registeredGeofence.longitude
+ + ", radius: " + registeredGeofence.radiusMeters + "m"
+ )
+ )
+ }
+ val firstGeofence = registeredGeofences[0]
+ cameraPosition.target(LatLng(firstGeofence.latitude, firstGeofence.longitude))
+ } else {
+ // NYC
+ cameraPosition.target(LatLng(40.730610, -73.935242))
+ }
+ googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition.build()))
+ }
+
+ companion object {
+ private val TAG = getBrazeLogTag(GeofencesMapActivity::class.java)
+ private const val REGISTERED_GEOFENCE_SHARED_PREFS_LOCATION = "com.appboy.support.geofences"
+
+ // Note that this is for testing purposes only. This storage location and format are not a supported API.
+ private fun retrieveBrazeGeofencesFromLocalStorage(sharedPreferences: SharedPreferences): List {
+ val geofences: MutableList = ArrayList()
+ val storedGeofences = sharedPreferences.all
+ if (storedGeofences.isNullOrEmpty()) {
+ brazelog(TAG) { "Did not find stored geofences." }
+ return geofences
+ }
+ val keys: Set = storedGeofences.keys
+ for (key in keys) {
+ val geofenceString = sharedPreferences.getString(key, null) ?: continue
+ try {
+ val geofenceJson = JSONObject(geofenceString)
+ val brazeGeofence = BrazeGeofence(geofenceJson)
+ geofences.add(brazeGeofence)
+ } catch (e: JSONException) {
+ brazelog(TAG, E, e) { "Encountered Json exception while parsing stored geofence: $geofenceString" }
+ } catch (e: Exception) {
+ brazelog(TAG, E, e) { "Encountered unexpected exception while parsing stored geofence: $geofenceString" }
+ }
+ }
+ return geofences
+ }
+ }
+}
diff --git a/droidboy/src/main/java/com/appboy/sample/activity/InAppMessageSandboxActivity.kt b/droidboy/src/main/java/com/appboy/sample/activity/InAppMessageSandboxActivity.kt
index bd5930b79d..701331a2d6 100644
--- a/droidboy/src/main/java/com/appboy/sample/activity/InAppMessageSandboxActivity.kt
+++ b/droidboy/src/main/java/com/appboy/sample/activity/InAppMessageSandboxActivity.kt
@@ -9,6 +9,7 @@ import com.appboy.sample.R
import com.braze.enums.inappmessage.DismissType
import com.braze.models.inappmessage.InAppMessageHtml
import com.braze.models.inappmessage.InAppMessageModal
+import com.braze.models.inappmessage.InAppMessageSlideup
import com.braze.models.inappmessage.MessageButton
import com.braze.ui.inappmessage.BrazeInAppMessageManager
import java.util.Random
@@ -26,7 +27,7 @@ class InAppMessageSandboxActivity : AppCompatActivity() {
findViewById(R.id.bSandboxDisplayMessage1).setOnClickListener { this.displayMessage(1) }
findViewById(R.id.bSandboxDisplayMessage0).setOnClickListener { this.displayMessage(0) }
findViewById(R.id.bSandboxDummyButton).setOnClickListener { Toast.makeText(this, "dummy button pressed!", Toast.LENGTH_SHORT).show() }
-
+ findViewById(R.id.bSandboxDisplaySlideup).setOnClickListener { displaySlideup() }
findViewById(R.id.bSandboxHtmlInApp).setOnClickListener { displayHtmlMessage() }
}
@@ -60,6 +61,15 @@ class InAppMessageSandboxActivity : AppCompatActivity() {
BrazeInAppMessageManager.getInstance().requestDisplayInAppMessage()
}
+ private fun displaySlideup() {
+ val slideup = InAppMessageSlideup()
+ slideup.message = "Welcome to Braze! This is a slideup in-app message."
+ slideup.icon = "\uf091"
+ slideup.dismissType = DismissType.MANUAL
+ BrazeInAppMessageManager.getInstance().addInAppMessage(slideup)
+ BrazeInAppMessageManager.getInstance().requestDisplayInAppMessage()
+ }
+
private fun displayHtmlMessage() {
val htmlString = this.assets.open(THE_WAY_HTML).bufferedReader().use {
it.readText()
diff --git a/droidboy/src/main/res/layout/activity_in_app_message_sandbox.xml b/droidboy/src/main/res/layout/activity_in_app_message_sandbox.xml
index b5af57260a..424cfd50e4 100644
--- a/droidboy/src/main/res/layout/activity_in_app_message_sandbox.xml
+++ b/droidboy/src/main/res/layout/activity_in_app_message_sandbox.xml
@@ -51,4 +51,13 @@
android:text="HTML IAM"
app:layout_constraintBottom_toTopOf="@+id/bSandboxDummyButton"
app:layout_constraintStart_toStartOf="parent" />
+
diff --git a/gradle.properties b/gradle.properties
index 01d5e329e3..7ea53cdac0 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,7 +3,7 @@
org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# Google Play Services
-PLAY_SERVICES_LOCATION_VERSION=21.0.1
+PLAY_SERVICES_LOCATION_VERSION=21.3.0
PLAY_SERVICES_MAPS_VERSION=17.0.0
PLAY_SERVICES_TAG_MANAGER=17.0.0
@@ -36,7 +36,7 @@ FIREBASE_CRASHLYTICS_VERSION=18.2.7
FIREBASE_CRASHLYTICS_GRADLE_VERSION=2.3.0
# Braze
-BRAZE_SDK_VERSION=32.0.0
+BRAZE_SDK_VERSION=32.1.0
# Kotlin
KOTLIN_VERSION=1.8.10
diff --git a/samples/firebase-push/google-services.json b/samples/firebase-push/google-services.json
index 7ec316f343..a6156ea572 100644
--- a/samples/firebase-push/google-services.json
+++ b/samples/firebase-push/google-services.json
@@ -21,7 +21,7 @@
],
"api_key": [
{
- "current_key": ""
+ "current_key": "AIzaSyDYvseENe5UlXILj32yYFPhiUawZbG8Y40"
}
],
"services": {
diff --git a/samples/google-tag-manager/google-services.json b/samples/google-tag-manager/google-services.json
index 4adb8fe8a2..05fa56f383 100644
--- a/samples/google-tag-manager/google-services.json
+++ b/samples/google-tag-manager/google-services.json
@@ -21,7 +21,7 @@
],
"api_key": [
{
- "current_key": ""
+ "current_key": "AIzaSyAKEcQJo8VRPtN9hCgkF0vYTeGdqfMdMF8"
}
],
"services": {
diff --git a/samples/hello-braze/proguard-rules.pro b/samples/hello-braze/proguard-rules.pro
new file mode 100644
index 0000000000..d04f6cba35
--- /dev/null
+++ b/samples/hello-braze/proguard-rules.pro
@@ -0,0 +1,18 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in android-sdk/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Keeping classes in com.appboy.ui and com.appboy.services because not keeping
+# them can cause build failures for users using Google Play Services with
+# Braze.
+-keepnames class com.appboy.ui.** { *; }
+-keep class com.appboy.services.** { *; }
+
+-dontwarn com.amazon.device.messaging.**
+-dontwarn bo.app.**
+-dontwarn com.appboy.ui.**