Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NT-1830: Braze basic push notification integration #1199

Merged
Merged
Show file tree
Hide file tree
Changes from 16 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
6 changes: 3 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ buildscript {
}

dependencies {
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.2'
}
}

Expand Down Expand Up @@ -238,9 +238,9 @@ dependencies {
implementation "net.danlew:android.joda:2.9.3"
implementation "org.jsoup:jsoup:1.12.1"

// Analytics
// Analytics Segment-Braze
implementation 'com.segment.analytics.android:analytics:4.9.0'
implementation "com.appboy:appboy-segment-integration:7.0.0"
implementation 'com.appboy:appboy-segment-integration:7.0.0'

// Security
implementation 'com.perimeterx.sdk:msdk:1.13.1'
Expand Down
7 changes: 3 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="www.kickstarter.com"
android:host="*.kickstarter.com"
android:pathPrefix="/projects/"
android:scheme="ksr" />
</intent-filter>
Expand Down Expand Up @@ -268,15 +268,15 @@
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service> <!-- Processes token refresh events from Friebase (e.g. token expired). Registers a new token. -->
</service>
<!-- Processes token refresh events from Firebase (e.g. token expired). Registers a new token. -->
<service
android:name=".services.firebase.TokenListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

<provider
android:name="com.facebook.FacebookContentProvider"
android:authorities="com.facebook.app.FacebookContentProvider${applicationId}"
Expand All @@ -298,5 +298,4 @@
android:name="firebase_performance_logcat_enabled"
android:value="${performanceLogs}" />
</application>

</manifest>
22 changes: 20 additions & 2 deletions app/src/main/java/com/kickstarter/ApplicationModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import com.kickstarter.libs.PushNotifications;
import com.kickstarter.libs.SegmentTrackingClient;
import com.kickstarter.libs.TrackingClientType;
import com.kickstarter.libs.braze.BrazeClient;
import com.kickstarter.libs.braze.RemotePushClientType;
import com.kickstarter.libs.graphql.DateAdapter;
import com.kickstarter.libs.graphql.DateTimeAdapter;
import com.kickstarter.libs.graphql.Iso8601DateTimeAdapter;
Expand Down Expand Up @@ -94,6 +96,7 @@
import java.util.Arrays;
import java.util.List;

import javax.annotation.Nonnull;
import javax.inject.Singleton;

import androidx.annotation.NonNull;
Expand Down Expand Up @@ -180,6 +183,7 @@ static Environment provideEnvironment(final @NonNull @ActivitySamplePreference I
@Singleton
static Analytics provideSegment(final @NonNull Build build, final @ApplicationContext @NonNull Context context) {
String apiKey = "";

Analytics segmentClient = null;

if (build.isRelease() && Build.isExternal()) {
Expand All @@ -201,6 +205,19 @@ static Analytics provideSegment(final @NonNull Build build, final @ApplicationCo
return segmentClient;
}

@Provides
@Nonnull
@Singleton
static RemotePushClientType provideBrazeClient(final @NonNull Build build, final @ApplicationContext @NonNull Context context) {
final BrazeClient brazeClient = new BrazeClient(context, build);

if (context instanceof KSApplication && !((KSApplication) context).isInUnitTests()) {
brazeClient.init();
}

return brazeClient;
}

@Provides
@Singleton
@NonNull
Expand Down Expand Up @@ -506,8 +523,9 @@ static CurrentUserType provideCurrentUser(final @AccessTokenPreference @NonNull
@Singleton
@NonNull
static DeviceRegistrarType provideDeviceRegistrar(final @NonNull PlayServicesCapability playServicesCapability,
final @ApplicationContext @NonNull Context context) {
return new DeviceRegistrar(playServicesCapability, context);
final @ApplicationContext @NonNull Context context,
final @NonNull RemotePushClientType brazeClient) {
return new DeviceRegistrar(playServicesCapability, context, brazeClient);
}

@Provides
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/com/kickstarter/KSApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.google.firebase.iid.FirebaseInstanceId;
import com.kickstarter.libs.ApiEndpoint;
import com.kickstarter.libs.PushNotifications;
import com.kickstarter.libs.braze.RemotePushClientType;
import com.kickstarter.libs.utils.ApplicationLifecycleUtil;
import com.kickstarter.libs.utils.Secrets;

Expand All @@ -30,6 +31,7 @@ public class KSApplication extends MultiDexApplication {
private ApplicationComponent component;
@Inject protected CookieManager cookieManager;
@Inject protected PushNotifications pushNotifications;
@Inject protected RemotePushClientType remotePushClientType;

@Override
@CallSuper
Expand Down Expand Up @@ -66,6 +68,9 @@ private void initApplication() {
final ApplicationLifecycleUtil appUtil = new ApplicationLifecycleUtil(this);
registerActivityLifecycleCallbacks(appUtil);
registerComponentCallbacks(appUtil);

// - Register lifecycle callback for Braze
registerActivityLifecycleCallbacks(this.remotePushClientType.getLifeCycleCallbacks());
}

public ApplicationComponent component() {
Expand Down
7 changes: 6 additions & 1 deletion app/src/main/java/com/kickstarter/libs/DeviceRegistrar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.work.workDataOf
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.messaging.FirebaseMessaging
import com.kickstarter.libs.braze.RemotePushClientType
import com.kickstarter.libs.qualifiers.ApplicationContext
import com.kickstarter.libs.utils.PlayServicesCapability
import com.kickstarter.libs.utils.WorkUtils
Expand All @@ -19,7 +20,8 @@ import java.util.concurrent.TimeUnit

class DeviceRegistrar(
private val playServicesCapability: PlayServicesCapability,
@param:ApplicationContext @field:ApplicationContext private val context: Context
@param:ApplicationContext @field:ApplicationContext private val context: Context,
private val pushClientType: RemotePushClientType
) : DeviceRegistrarType {

/**
Expand All @@ -39,6 +41,9 @@ class DeviceRegistrar(
// Get new FCM registration token
val token = task.result
registerToken(this.context, token)

// - Braze register push notification token
this.pushClientType.registerPushMessages(this.context, token)
}
)
}
Expand Down
96 changes: 96 additions & 0 deletions app/src/main/java/com/kickstarter/libs/braze/BrazeClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.kickstarter.libs.braze

import android.app.Application
import android.content.Context
import android.util.Log
import com.appboy.Appboy
import com.appboy.AppboyFirebaseMessagingService
import com.appboy.AppboyLifecycleCallbackListener
import com.appboy.configuration.AppboyConfig
import com.appboy.support.AppboyLogger
import com.google.firebase.messaging.RemoteMessage
import com.kickstarter.libs.Build
import com.kickstarter.libs.utils.Secrets

/**
* Remote PushNotifications specification
*/
interface RemotePushClientType {
/**
* Initial configuration requirements
*/
fun init()

/**
* Should return the ID sender specific for each environment
*/
fun getIdSender(): String

/**
* Register a Push token, should be called when
* FirebaseMessaging.getInstance().token.addOnCompleteListener has a successful new token
*/
fun registerPushMessages(context: Context, token: String)

/**
* Should be called on Firebase.MessageService.onMessageReceived
*
* @return true if the message is a braze message
* @return false if the message is not from braze
*/
fun handleRemoteMessages(context: Context, message: RemoteMessage): Boolean

/**
* Application Lifecycle events Callbacks,
* Should be registered on Application.OnCreate
*/
fun getLifeCycleCallbacks(): Application.ActivityLifecycleCallbacks
}

/**
* Braze client SDK wrapper class.
* @param context It needs the application context to be initialized,
* @param build the type of build will determine the IdSender from Firebase and the logs mode
*/
open class BrazeClient(
private val context: Context,
private val build: Build
) : RemotePushClientType {

override fun init() {

val appBoyConfig = AppboyConfig.Builder()
.setIsFirebaseCloudMessagingRegistrationEnabled(true)
.setFirebaseCloudMessagingSenderIdKey(getIdSender())
// .setDefaultNotificationChannelName("General") --> TODO: Define notification channels for the new push notifications types
// .setDefaultNotificationChannelDescription("Braze related push")
.setHandlePushDeepLinksAutomatically(true)
.build()
Appboy.configure(context, appBoyConfig)

if (this.build.isDebug || Build.isInternal()) {
AppboyLogger.setLogLevel(Log.VERBOSE)
}
}

override fun getIdSender(): String {
var senderId = ""
if (build.isRelease && Build.isExternal()) {
senderId = Secrets.FirebaseSenderID.PRODUCTION
}
if (build.isDebug || Build.isInternal()) {
senderId = Secrets.FirebaseSenderID.STAGING
}

return senderId
}

override fun registerPushMessages(context: Context, token: String) =
Appboy.getInstance(context).registerAppboyPushMessages(token)

override fun handleRemoteMessages(context: Context, message: RemoteMessage) =
AppboyFirebaseMessagingService.handleBrazeRemoteMessage(context, message)

override fun getLifeCycleCallbacks(): Application.ActivityLifecycleCallbacks =
AppboyLifecycleCallbackListener(true, false)
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public final class Secrets {
public static final String PRODUCTION = "segment-production";
}

public static final class FirebaseSenderID {
public static final String STAGING = "firebase-internal-release";
public static final String PRODUCTION = "firebase-external-release";
}

public static final class RegExpPattern {
public static final Pattern API = Pattern.compile("\\Aapi\\z");
public static final Pattern HIVEQUEEN = Pattern.compile("\\Adev\\z");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.kickstarter.KSApplication;
import com.kickstarter.R;
import com.kickstarter.libs.PushNotifications;
import com.kickstarter.libs.braze.RemotePushClientType;
import com.kickstarter.models.pushdata.Activity;
import com.kickstarter.models.pushdata.GCM;
import com.kickstarter.services.apiresponses.PushNotificationEnvelope;
Expand All @@ -23,6 +24,7 @@
public class MessageService extends FirebaseMessagingService {
@Inject protected Gson gson;
@Inject protected PushNotifications pushNotifications;
@Inject protected RemotePushClientType remotePushClientType;

@Override
public void onNewToken(@NonNull final String s) {
Expand All @@ -37,37 +39,43 @@ public void onCreate() {

/**
* Called when a message is received from Firebase.
* - If the message comes from braze it will be handle on:
* - @see RemotePushClientType#handleRemoteMessages(android.content.Context, com.google.firebase.messaging.RemoteMessage)
* - If the message is from Kickstarter it will be process here
*
* @param remoteMessage Object containing message information.
*/
@Override
public void onMessageReceived(final RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
final Boolean isBrazeMessage = this.remotePushClientType.handleRemoteMessages(this, remoteMessage);

final String senderId = getString(R.string.gcm_defaultSenderId);
final String from = remoteMessage.getFrom();
if (!TextUtils.equals(from, senderId)) {
Timber.e("Received a message from %s, expecting %s", from, senderId);
return;
}
if (!isBrazeMessage) {
final String senderId = getString(R.string.gcm_defaultSenderId);
final String from = remoteMessage.getFrom();
if (!TextUtils.equals(from, senderId)) {
Timber.e("Received a message from %s, expecting %s", from, senderId);
return;
}

final Map<String, String> data = remoteMessage.getData();
final Map<String, String> data = remoteMessage.getData();

final PushNotificationEnvelope envelope = PushNotificationEnvelope.builder()
.activity(this.gson.fromJson(data.get("activity"), Activity.class))
.erroredPledge(this.gson.fromJson(data.get("errored_pledge"), PushNotificationEnvelope.ErroredPledge.class))
.gcm(this.gson.fromJson(data.get("gcm"), GCM.class))
.message(this.gson.fromJson(data.get("message"), PushNotificationEnvelope.Message.class))
.project(this.gson.fromJson(data.get("project"), PushNotificationEnvelope.Project.class))
.survey(this.gson.fromJson(data.get("survey"), PushNotificationEnvelope.Survey.class))
.build();
final PushNotificationEnvelope envelope = PushNotificationEnvelope.builder()
.activity(this.gson.fromJson(data.get("activity"), Activity.class))
.erroredPledge(this.gson.fromJson(data.get("errored_pledge"), PushNotificationEnvelope.ErroredPledge.class))
.gcm(this.gson.fromJson(data.get("gcm"), GCM.class))
.message(this.gson.fromJson(data.get("message"), PushNotificationEnvelope.Message.class))
.project(this.gson.fromJson(data.get("project"), PushNotificationEnvelope.Project.class))
.survey(this.gson.fromJson(data.get("survey"), PushNotificationEnvelope.Survey.class))
.build();

if (envelope == null) {
Timber.e("Cannot parse message, malformed or unexpected data: %s", data.toString());
return;
}
if (envelope == null) {
Timber.e("Cannot parse message, malformed or unexpected data: %s", data.toString());
return;
}

Timber.d("Received message: %s", envelope.toString());
this.pushNotifications.add(envelope);
Timber.d("Received message: %s", envelope.toString());
this.pushNotifications.add(envelope);
}
}
}
Loading