Panagiotis Triantafyllou

deh part7

...@@ -78,22 +78,22 @@ ...@@ -78,22 +78,22 @@
78 android:exported="false" /> 78 android:exported="false" />
79 79
80 <!-- FCM Service for push notifications --> 80 <!-- FCM Service for push notifications -->
81 - <service 81 +<!-- <service-->
82 - android:name="ly.warp.sdk.services.FCMBaseMessagingService" 82 +<!-- android:name="ly.warp.sdk.services.FCMBaseMessagingService"-->
83 - android:exported="false"> 83 +<!-- android:exported="false">-->
84 - <intent-filter> 84 +<!-- <intent-filter>-->
85 - <action android:name="com.google.firebase.MESSAGING_EVENT" /> 85 +<!-- <action android:name="com.google.firebase.MESSAGING_EVENT" />-->
86 - </intent-filter> 86 +<!-- </intent-filter>-->
87 - </service> 87 +<!-- </service>-->
88 88
89 <!-- Service used for handling Huawei Push Notifications, comment if we are in Google build --> 89 <!-- Service used for handling Huawei Push Notifications, comment if we are in Google build -->
90 - <service 90 +<!-- <service-->
91 - android:name="ly.warp.sdk.services.HMSBaseMessagingService" 91 +<!-- android:name="ly.warp.sdk.services.HMSBaseMessagingService"-->
92 - android:exported="false"> 92 +<!-- android:exported="false">-->
93 - <intent-filter> 93 +<!-- <intent-filter>-->
94 - <action android:name="com.huawei.push.action.MESSAGING_EVENT" /> 94 +<!-- <action android:name="com.huawei.push.action.MESSAGING_EVENT" />-->
95 - </intent-filter> 95 +<!-- </intent-filter>-->
96 - </service> 96 +<!-- </service>-->
97 97
98 <receiver 98 <receiver
99 android:name="ly.warp.sdk.receivers.ConnectivityChangedReceiver" 99 android:name="ly.warp.sdk.receivers.ConnectivityChangedReceiver"
......
1 +package ly.warp.sdk.utils.managers;
2 +
3 +import static ly.warp.sdk.utils.constants.WarpConstants.CHANNEL_ID;
4 +import static ly.warp.sdk.utils.constants.WarpConstants.RANDOM_MAX;
5 +import static ly.warp.sdk.utils.constants.WarpConstants.RANDOM_MIN;
6 +
7 +import android.app.Notification;
8 +import android.app.NotificationChannel;
9 +import android.app.NotificationManager;
10 +import android.app.PendingIntent;
11 +import android.content.Context;
12 +import android.content.Intent;
13 +import android.content.pm.PackageManager;
14 +import android.graphics.Bitmap;
15 +import android.graphics.BitmapFactory;
16 +import android.graphics.Color;
17 +import android.media.RingtoneManager;
18 +import android.os.Build;
19 +import android.os.Bundle;
20 +import android.text.TextUtils;
21 +import android.widget.RemoteViews;
22 +
23 +import androidx.annotation.NonNull;
24 +import androidx.core.app.NotificationCompat;
25 +import androidx.work.Constraints;
26 +import androidx.work.NetworkType;
27 +import androidx.work.OneTimeWorkRequest;
28 +import androidx.work.WorkManager;
29 +
30 +import com.google.firebase.messaging.RemoteMessage;
31 +
32 +import org.apache.http.HttpStatus;
33 +import org.json.JSONException;
34 +import org.json.JSONObject;
35 +
36 +import java.io.IOException;
37 +import java.io.InputStream;
38 +import java.net.HttpURLConnection;
39 +import java.net.URL;
40 +import java.security.SecureRandom;
41 +import java.util.List;
42 +import java.util.concurrent.ThreadLocalRandom;
43 +import java.util.concurrent.TimeUnit;
44 +
45 +import ly.warp.sdk.Warply;
46 +import ly.warp.sdk.activities.WarpViewActivity;
47 +import ly.warp.sdk.io.models.PushCampaign;
48 +import ly.warp.sdk.services.PushEventsWorkerService;
49 +import ly.warp.sdk.utils.WarpUtils;
50 +import ly.warp.sdk.utils.WarplyProperty;
51 +
52 +/**
53 + * Delegate class for handling Firebase Cloud Messaging (FCM) push notifications.
54 + * This class allows client apps with existing FCM implementations to integrate Warply push handling.
55 + *
56 + * Usage:
57 + * 1. Create an instance in your FirebaseMessagingService
58 + * 2. Call handleMessage() from onMessageReceived()
59 + * 3. Call handleNewToken() from onNewToken()
60 + */
61 +public class WarplyFCMPushDelegate {
62 +
63 + // ===========================================================
64 + // Constants
65 + // ===========================================================
66 +
67 + private static final String KEY_TITLE = "alert";
68 + private static final String KEY_SUBTITLE = "subtitle";
69 + private static final String KEY_TICKER = "alert";
70 + private static final String KEY_MESSAGE = "message";
71 + private static final String KEY_PAYLOAD = "payload";
72 +
73 + // ===========================================================
74 + // Fields
75 + // ===========================================================
76 +
77 + private final Context context;
78 +
79 + // ===========================================================
80 + // Constructors
81 + // ===========================================================
82 +
83 + /**
84 + * Creates a new WarplyFCMPushDelegate
85 + * @param context Application context
86 + */
87 + public WarplyFCMPushDelegate(@NonNull Context context) {
88 + this.context = context;
89 + }
90 +
91 + // ===========================================================
92 + // Public Methods
93 + // ===========================================================
94 +
95 + /**
96 + * Call this method from your FirebaseMessagingService.onMessageReceived()
97 + * @param remoteMessage The FCM message
98 + * @return true if message was handled by Warply, false if it should be processed by your app
99 + */
100 + public boolean handleMessage(@NonNull RemoteMessage remoteMessage) {
101 + Bundle data = remoteMessage.toIntent().getExtras();
102 +
103 + // Check if this message is for Warply
104 + if (data == null || !data.containsKey("loyalty-action")) {
105 + return false; // Not a Warply message, let the client handle it
106 + }
107 +
108 + // This is a Warply message, process it
109 + PushCampaign pc = new PushCampaign(data);
110 +
111 + // Set up analytics tracking
112 + setUpPushEvents(pc);
113 +
114 + WarpUtils.log("Warply received push with action: " + pc.getAction());
115 + if (pc.getAction() == 0 && !pc.hasActions()) {
116 + showCampaignNotification(context, pc);
117 + } else {
118 + showDefaultNotification(context, data);
119 + }
120 +
121 + return true; // Message was handled by Warply
122 + }
123 +
124 + /**
125 + * Call this method from your FirebaseMessagingService.onNewToken()
126 + * @param token The new FCM token
127 + */
128 + public void handleNewToken(@NonNull String token) {
129 + WarpUtils.setDeviceToken(context, token);
130 + }
131 +
132 + // ===========================================================
133 + // Private Methods
134 + // ===========================================================
135 +
136 + private void setUpPushEvents(PushCampaign pc) {
137 + Warply.getInitializer(context).init();
138 + WarplyAnalyticsManager.logUserReceivedPush(pc);
139 +
140 + Constraints constraints = new Constraints.Builder()
141 + .setRequiredNetworkType(NetworkType.CONNECTED)
142 + .build();
143 +
144 + OneTimeWorkRequest sendEvent = new OneTimeWorkRequest.Builder(PushEventsWorkerService.class)
145 + .setConstraints(constraints)
146 + .setInitialDelay(defineRandomStart(), TimeUnit.MINUTES)
147 + .build();
148 +
149 + WorkManager.getInstance(context).enqueue(sendEvent);
150 + }
151 +
152 + private int defineRandomStart() {
153 + if (Build.VERSION.SDK_INT >= 21) {
154 + return ThreadLocalRandom.current().nextInt(RANDOM_MIN, RANDOM_MAX + 1);
155 + } else {
156 + SecureRandom rand = new SecureRandom();
157 + return rand.nextInt((RANDOM_MAX - RANDOM_MIN) + 1) + RANDOM_MIN;
158 + }
159 + }
160 +
161 + // ===========================================================
162 + // Notification Methods
163 + // ===========================================================
164 +
165 + /**
166 + * Shows a campaign notification
167 + */
168 + private void showCampaignNotification(Context context, PushCampaign pc) {
169 + showCampaignNotification(context, pc, null);
170 + }
171 +
172 + /**
173 + * Shows a campaign notification with actions
174 + */
175 + private void showCampaignNotification(Context context, PushCampaign pc, List<NotificationCompat.Action> actions) {
176 + showCampaignNotification(context, pc, null, null);
177 + }
178 +
179 + /**
180 + * Shows a campaign notification with actions and custom view
181 + */
182 + private void showCampaignNotification(Context context, PushCampaign pc, List<NotificationCompat.Action> actions, RemoteViews remoteViews) {
183 + WarpUtils.log("Showing campaign with session UUID: " + pc.getSessionUUID());
184 +
185 + SecureRandom randomGenerator = new SecureRandom();
186 +
187 + int uid = randomGenerator.nextInt(1000);
188 + if (!TextUtils.isEmpty(pc.getSessionUUID())) {
189 + uid = 0;
190 + }
191 +
192 + Intent newIntent = WarpViewActivity.createIntentFromSessionUUID(
193 + context, pc.getSessionUUID(), "from_notification_status");
194 + newIntent.setAction(Long.toString(System.currentTimeMillis()));
195 +
196 + NotificationCompat.Builder b = new NotificationCompat.Builder(context, CHANNEL_ID);
197 +
198 + if (pc.getNotificationStyle().equalsIgnoreCase(PushCampaign.NOTIFICATION_STYLE_BIG_PICTURE)
199 + && !TextUtils.isEmpty(pc.getBigPictureUrl())) {
200 + //get big picture style
201 + NotificationCompat.BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
202 + bigPictureStyle.bigPicture(loadDrawable(pc.getBigPictureUrl()));
203 + if (!TextUtils.isEmpty(pc.getSubtitle())) {
204 + bigPictureStyle.setSummaryText(pc.getSubtitle());
205 + }
206 + b.setStyle(bigPictureStyle);
207 + } else if (pc.getNotificationStyle().equalsIgnoreCase(PushCampaign.NOTIFICATION_STYLE_BIG_TEXT)
208 + && !TextUtils.isEmpty(pc.getBigText())) {
209 + //get big picture style
210 + NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle();
211 + bigTextStyle.bigText(pc.getBigText());
212 + if (!TextUtils.isEmpty(pc.getSummaryText())) {
213 + bigTextStyle.setSummaryText(pc.getSummaryText());
214 + }
215 +
216 + b.setStyle(bigTextStyle);
217 + } else if (pc.getNotificationStyle().equalsIgnoreCase(PushCampaign.NOTIFICATION_STYLE_INBOX_STYLE)) {
218 + NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
219 + for (int i = 0; i < pc.getInboxLines().size(); i++) {
220 + inboxStyle.addLine(pc.getInboxLines().get(i));
221 + }
222 + if (!TextUtils.isEmpty(pc.getSummaryText())) {
223 + inboxStyle.setSummaryText(pc.getSummaryText());
224 + }
225 + b.setStyle(inboxStyle);
226 + }
227 +
228 + b.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS);
229 + b.setTicker(pc.getTicker());
230 + b.setChannelId(CHANNEL_ID);
231 + b.setContentTitle(pc.getTitle());
232 + b.setContentText(pc.getSubtitle());
233 + b.setSubText(pc.getContent());
234 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
235 + b.setContentIntent(PendingIntent.getActivity(context, uid, newIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE));
236 + } else {
237 + b.setContentIntent(PendingIntent.getActivity(context, uid, newIntent, PendingIntent.FLAG_ONE_SHOT));
238 + }
239 + b.setAutoCancel(true);
240 + b.setOngoing(true);
241 + b.setAutoCancel(true);
242 + b.setSmallIcon(WarplyProperty.getPushIconResId(context));
243 + b.setLargeIcon(loadDrawable(pc.getImageUrl()));
244 +
245 + //Add remote view as content if exists
246 + if (remoteViews != null) {
247 + b.setContent(remoteViews);
248 + }
249 +
250 + if (pc.getSoundUri(context) == null)
251 + b.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));
252 + else
253 + b.setSound(pc.getSoundUri(context));
254 +
255 +
256 + //If push has color
257 + int color = WarplyProperty.getPushColor(context);
258 + if (!TextUtils.isEmpty(pc.getColor())) {
259 + try {
260 + color = Color.parseColor(pc.getColor());
261 + } catch (IllegalArgumentException iae) {
262 + iae.printStackTrace();
263 + }
264 + }
265 + b.setColor(color);
266 +
267 + //add actions
268 + if (actions != null) {
269 + if (actions.size() > 0) {
270 + for (NotificationCompat.Action action : actions) {
271 + b.addAction(action);
272 + }
273 + }
274 + }
275 +
276 + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
277 +
278 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm != null) {
279 + NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, "notification_channel", NotificationManager.IMPORTANCE_DEFAULT);
280 + nm.createNotificationChannel(notificationChannel);
281 + Notification notification_build_with_largeicon = b.setChannelId(CHANNEL_ID).build();
282 + notification_build_with_largeicon.flags = Notification.FLAG_AUTO_CANCEL; //notification with only large icon due to the two build notificaition is not canceled from bar
283 + nm.notify(pc.getSessionUUID(), uid, notification_build_with_largeicon);
284 + } else {
285 + Notification notification_build_with_largeicon = b.build();
286 + notification_build_with_largeicon.flags = Notification.FLAG_AUTO_CANCEL; //notification with only large icon due to the two build notificaition is not canceled from bar
287 + if (nm != null) {
288 + nm.notify(pc.getSessionUUID(), uid, notification_build_with_largeicon);
289 + }
290 + }
291 + }
292 +
293 + /**
294 + * Shows a default notification for the app
295 + */
296 + private void showDefaultNotification(Context context, Bundle data) {
297 + String title = getNotificationTitle(data);
298 + String ticker = getNotificationTicker(data);
299 + String message = getNotificationMessage(data);
300 +
301 + PushCampaign pc = new PushCampaign(data);
302 +
303 + if (pc != null) {
304 + PackageManager pm = context.getPackageManager();
305 + Intent newIntent = pm.getLaunchIntentForPackage(context.getPackageName());
306 +
307 + showNotification(context, pc, newIntent, null, null, null, 0);
308 + }
309 + }
310 +
311 + /**
312 + * Shows a notification with custom parameters
313 + */
314 + private void showNotification(Context context, PushCampaign pc, Intent newIntent, List<NotificationCompat.Action> actions, RemoteViews remoteViews, String tag, int id) {
315 + SecureRandom randomGenerator = new SecureRandom();
316 + int uid = randomGenerator.nextInt(1000);
317 + if (id > 0) {
318 + uid = id;
319 + }
320 + WarpUtils.log("Showing notification with tag: " + tag + " and id: " + id);
321 +
322 + NotificationCompat.Builder b = new NotificationCompat.Builder(context, CHANNEL_ID);
323 +
324 + if (pc.getNotificationStyle().equalsIgnoreCase(PushCampaign.NOTIFICATION_STYLE_BIG_PICTURE)
325 + && !TextUtils.isEmpty(pc.getBigPictureUrl())) {
326 + //get big picture style
327 + NotificationCompat.BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
328 + bigPictureStyle.bigPicture(loadDrawable(pc.getBigPictureUrl()));
329 + if (!TextUtils.isEmpty(pc.getSubtitle())) {
330 + bigPictureStyle.setSummaryText(pc.getSubtitle());
331 + }
332 + b.setStyle(bigPictureStyle);
333 + } else if (pc.getNotificationStyle().equalsIgnoreCase(PushCampaign.NOTIFICATION_STYLE_BIG_TEXT)
334 + && !TextUtils.isEmpty(pc.getBigText())) {
335 + //get big picture style
336 + NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle();
337 + bigTextStyle.bigText(pc.getBigText());
338 + if (!TextUtils.isEmpty(pc.getSummaryText())) {
339 + bigTextStyle.setSummaryText(pc.getSummaryText());
340 + }
341 +
342 + b.setStyle(bigTextStyle);
343 + } else if (pc.getNotificationStyle().equalsIgnoreCase(PushCampaign.NOTIFICATION_STYLE_INBOX_STYLE)) {
344 + NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
345 + for (int i = 0; i < pc.getInboxLines().size(); i++) {
346 + inboxStyle.addLine(pc.getInboxLines().get(i));
347 + }
348 + if (!TextUtils.isEmpty(pc.getSummaryText())) {
349 + inboxStyle.setSummaryText(pc.getSummaryText());
350 + }
351 + b.setStyle(inboxStyle);
352 + }
353 +
354 + b.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS);
355 + b.setTicker(pc.getTicker());
356 + b.setChannelId(CHANNEL_ID);
357 + b.setContentTitle(pc.getTitle());
358 + b.setContentText(pc.getSubtitle());
359 + b.setSubText(pc.getContent());
360 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
361 + b.setContentIntent(PendingIntent.getActivity(context, uid, newIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE));
362 + } else {
363 + b.setContentIntent(PendingIntent.getActivity(context, uid, newIntent, PendingIntent.FLAG_ONE_SHOT));
364 + }
365 + b.setAutoCancel(true);
366 + b.setOngoing(true);
367 + b.setAutoCancel(true);
368 + b.setSmallIcon(WarplyProperty.getPushIconResId(context));
369 + b.setLargeIcon(loadDrawable(pc.getImageUrl()));
370 +
371 + if (remoteViews != null) {
372 + b.setContent(remoteViews);
373 + }
374 +
375 + if (pc.getSoundUri(context) == null)
376 + b.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));
377 + else
378 + b.setSound(pc.getSoundUri(context));
379 +
380 + //If push has color
381 + int color = WarplyProperty.getPushColor(context);
382 + if (!TextUtils.isEmpty(pc.getColor())) {
383 + try {
384 + color = Color.parseColor(pc.getColor());
385 + } catch (IllegalArgumentException iae) {
386 + iae.printStackTrace();
387 + }
388 + }
389 + b.setColor(color);
390 +
391 + //add actions
392 + if (actions != null) {
393 + if (actions.size() > 0) {
394 + for (NotificationCompat.Action action : actions) {
395 + b.addAction(action);
396 + }
397 + }
398 + }
399 +
400 + newIntent.setAction(Long.toString(System.currentTimeMillis()));
401 + PendingIntent pi;
402 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
403 + pi = PendingIntent.getActivity(context, uid, newIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
404 + } else {
405 + pi = PendingIntent.getActivity(context, uid, newIntent, PendingIntent.FLAG_ONE_SHOT);
406 + }
407 + b.setContentIntent(pi);
408 +
409 + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
410 +
411 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm != null) {
412 + NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, "notification_channel", NotificationManager.IMPORTANCE_DEFAULT);
413 + nm.createNotificationChannel(notificationChannel);
414 + Notification notification_build = b.setChannelId(CHANNEL_ID).build();
415 + notification_build.flags = Notification.FLAG_AUTO_CANCEL;
416 + nm.notify(pc.getSessionUUID(), uid, notification_build);
417 + } else {
418 + Notification notification_build = b.build();
419 + notification_build.flags = Notification.FLAG_AUTO_CANCEL;
420 + if (nm != null) {
421 + nm.notify(pc.getSessionUUID(), uid, notification_build);
422 + }
423 + }
424 + }
425 +
426 + // ===========================================================
427 + // Helper Methods
428 + // ===========================================================
429 +
430 + public JSONObject getPayload(Bundle data) {
431 + String payloadStr = data.getString(KEY_PAYLOAD);
432 + try {
433 + JSONObject payload = new JSONObject(payloadStr);
434 + return payload;
435 + } catch (JSONException e) {
436 + WarpUtils.log("Error with payload", e);
437 + return null;
438 + }
439 + }
440 +
441 + public String getNotificationTitle(Bundle data) {
442 + return data.getString(KEY_TITLE);
443 + }
444 +
445 + public String getNotificationMessage(Bundle data) {
446 + return data.getString(KEY_MESSAGE);
447 + }
448 +
449 + public String getNotificationTicker(Bundle data) {
450 + return data.getString(KEY_TICKER);
451 + }
452 +
453 + public String getNotificationSubtitle(Bundle data) {
454 + return data.getString(KEY_SUBTITLE);
455 + }
456 +
457 + private static Bitmap loadDrawable(String urlString) {
458 + if (urlString == null || TextUtils.isEmpty(urlString))
459 + return null;
460 +
461 + URL url;
462 + HttpURLConnection urlConnection = null;
463 + try {
464 + url = new URL(urlString);
465 + urlConnection = (HttpURLConnection) url.openConnection();
466 +
467 + if (!(urlConnection.getResponseCode() == HttpStatus.SC_OK))
468 + return null;
469 +
470 + InputStream is = urlConnection.getInputStream();
471 + if (is == null) {
472 + urlConnection.disconnect();
473 + return null;
474 + }
475 +
476 + Bitmap b = BitmapFactory.decodeStream(is);
477 + is.close();
478 + urlConnection.disconnect();
479 + return b;
480 +
481 + } catch (IOException e) {
482 + return null;
483 + }
484 + }
485 +}
1 +package ly.warp.sdk.utils.managers;
2 +
3 +import static ly.warp.sdk.utils.constants.WarpConstants.CHANNEL_ID;
4 +import static ly.warp.sdk.utils.constants.WarpConstants.RANDOM_MAX;
5 +import static ly.warp.sdk.utils.constants.WarpConstants.RANDOM_MIN;
6 +
7 +import android.app.Notification;
8 +import android.app.NotificationChannel;
9 +import android.app.NotificationManager;
10 +import android.app.PendingIntent;
11 +import android.content.Context;
12 +import android.content.Intent;
13 +import android.content.pm.PackageManager;
14 +import android.graphics.Bitmap;
15 +import android.graphics.BitmapFactory;
16 +import android.graphics.Color;
17 +import android.media.RingtoneManager;
18 +import android.os.Build;
19 +import android.os.Bundle;
20 +import android.text.TextUtils;
21 +import android.widget.RemoteViews;
22 +
23 +import androidx.annotation.NonNull;
24 +import androidx.core.app.NotificationCompat;
25 +import androidx.work.Constraints;
26 +import androidx.work.NetworkType;
27 +import androidx.work.OneTimeWorkRequest;
28 +import androidx.work.WorkManager;
29 +
30 +import com.huawei.hms.push.RemoteMessage;
31 +
32 +import org.apache.http.HttpStatus;
33 +import org.json.JSONException;
34 +import org.json.JSONObject;
35 +
36 +import java.io.InputStream;
37 +import java.net.HttpURLConnection;
38 +import java.net.URL;
39 +import java.security.SecureRandom;
40 +import java.util.Iterator;
41 +import java.util.List;
42 +import java.util.concurrent.ThreadLocalRandom;
43 +import java.util.concurrent.TimeUnit;
44 +
45 +import ly.warp.sdk.Warply;
46 +import ly.warp.sdk.activities.WarpViewActivity;
47 +import ly.warp.sdk.io.models.PushCampaign;
48 +import ly.warp.sdk.services.PushEventsWorkerService;
49 +import ly.warp.sdk.utils.WarpUtils;
50 +import ly.warp.sdk.utils.WarplyProperty;
51 +
52 +/**
53 + * Delegate class for handling Huawei Mobile Services (HMS) push notifications.
54 + * This class allows client apps with existing HMS implementations to integrate Warply push handling.
55 + *
56 + * Usage:
57 + * 1. Create an instance in your HmsMessageService
58 + * 2. Call handleMessage() from onMessageReceived()
59 + * 3. Call handleNewToken() from onNewToken()
60 + */
61 +public class WarplyHMSPushDelegate {
62 +
63 + // ===========================================================
64 + // Constants
65 + // ===========================================================
66 +
67 + private static final String KEY_TITLE = "alert";
68 + private static final String KEY_SUBTITLE = "subtitle";
69 + private static final String KEY_TICKER = "alert";
70 + private static final String KEY_MESSAGE = "message";
71 + private static final String KEY_PAYLOAD = "payload";
72 +
73 + // ===========================================================
74 + // Fields
75 + // ===========================================================
76 +
77 + private final Context context;
78 + private static Bitmap bFinal = null;
79 +
80 + // ===========================================================
81 + // URLThread class
82 + // ===========================================================
83 + public static class URLThread extends Thread {
84 + private String urlString = "";
85 +
86 + public URLThread(String url) {
87 + this.urlString = url;
88 + }
89 +
90 + @Override
91 + public void run() {
92 + try {
93 + URL url;
94 + HttpURLConnection urlConnection = null;
95 + url = new URL(urlString);
96 + urlConnection = (HttpURLConnection) url.openConnection();
97 + if (!(urlConnection.getResponseCode() == HttpStatus.SC_OK)) {
98 + bFinal = null;
99 + return;
100 + }
101 + InputStream is = urlConnection.getInputStream();
102 + if (is == null) {
103 + urlConnection.disconnect();
104 + bFinal = null;
105 + return;
106 + }
107 + Bitmap b = BitmapFactory.decodeStream(is);
108 + is.close();
109 + urlConnection.disconnect();
110 + bFinal = b;
111 + } catch (Exception ex) {
112 + bFinal = null;
113 + ex.printStackTrace();
114 + }
115 + }
116 + }
117 +
118 + // ===========================================================
119 + // Constructors
120 + // ===========================================================
121 +
122 + /**
123 + * Creates a new WarplyHMSPushDelegate
124 + * @param context Application context
125 + */
126 + public WarplyHMSPushDelegate(@NonNull Context context) {
127 + this.context = context;
128 + }
129 +
130 + // ===========================================================
131 + // Public Methods
132 + // ===========================================================
133 +
134 + /**
135 + * Call this method from your HmsMessageService.onMessageReceived()
136 + * @param remoteMessage The HMS message
137 + * @return true if message was handled by Warply, false if it should be processed by your app
138 + */
139 + public boolean handleMessage(@NonNull RemoteMessage remoteMessage) {
140 + JSONObject data = new JSONObject(remoteMessage.getDataOfMap());
141 +
142 + // Check if this message is for Warply
143 + if (data == null || !data.has("loyalty-action")) {
144 + return false; // Not a Warply message, let the client handle it
145 + }
146 +
147 + // This is a Warply message, process it
148 + PushCampaign pc = new PushCampaign(data);
149 +
150 + // Set up analytics tracking
151 + setUpPushEvents(pc);
152 +
153 + WarpUtils.log("Warply received push with action: " + pc.getAction());
154 + if (pc.getAction() == 0 && !pc.hasActions()) {
155 + showCampaignNotification(context, pc);
156 + } else {
157 + Bundle bundledData = null;
158 + try {
159 + bundledData = jsonToBundle(data);
160 + } catch (JSONException e) {
161 + e.printStackTrace();
162 + }
163 + showDefaultNotification(context, bundledData);
164 + }
165 +
166 + return true; // Message was handled by Warply
167 + }
168 +
169 + /**
170 + * Call this method from your HmsMessageService.onNewToken()
171 + * @param token The new HMS token
172 + */
173 + public void handleNewToken(@NonNull String token) {
174 + WarpUtils.setDeviceToken(context, token);
175 + }
176 +
177 + // ===========================================================
178 + // Private Methods
179 + // ===========================================================
180 +
181 + private void setUpPushEvents(PushCampaign pc) {
182 + Warply.getInitializer(context).init();
183 + WarplyAnalyticsManager.logUserReceivedPush(pc);
184 +
185 + Constraints constraints = new Constraints.Builder()
186 + .setRequiredNetworkType(NetworkType.CONNECTED)
187 + .build();
188 +
189 + OneTimeWorkRequest sendEvent = new OneTimeWorkRequest.Builder(PushEventsWorkerService.class)
190 + .setConstraints(constraints)
191 + .setInitialDelay(defineRandomStart(), TimeUnit.MINUTES)
192 + .build();
193 +
194 + WorkManager.getInstance(context).enqueue(sendEvent);
195 + }
196 +
197 + private int defineRandomStart() {
198 + if (Build.VERSION.SDK_INT >= 21) {
199 + return ThreadLocalRandom.current().nextInt(RANDOM_MIN, RANDOM_MAX + 1);
200 + } else {
201 + SecureRandom rand = new SecureRandom();
202 + return rand.nextInt((RANDOM_MAX - RANDOM_MIN) + 1) + RANDOM_MIN;
203 + }
204 + }
205 +
206 + // ===========================================================
207 + // Notification Methods
208 + // ===========================================================
209 +
210 + /**
211 + * Shows a campaign notification
212 + */
213 + private void showCampaignNotification(Context context, PushCampaign pc) {
214 + showCampaignNotification(context, pc, null);
215 + }
216 +
217 + /**
218 + * Shows a campaign notification with actions
219 + */
220 + private void showCampaignNotification(Context context, PushCampaign pc, List<NotificationCompat.Action> actions) {
221 + showCampaignNotification(context, pc, null, null);
222 + }
223 +
224 + /**
225 + * Shows a campaign notification with actions and custom view
226 + */
227 + private void showCampaignNotification(Context context, PushCampaign pc, List<NotificationCompat.Action> actions, RemoteViews remoteViews) {
228 + WarpUtils.log("Showing campaign with session UUID: " + pc.getSessionUUID());
229 +
230 + SecureRandom randomGenerator = new SecureRandom();
231 +
232 + int uid = randomGenerator.nextInt(1000);
233 + if (!TextUtils.isEmpty(pc.getSessionUUID())) {
234 + uid = 0;
235 + }
236 +
237 + Intent newIntent = WarpViewActivity.createIntentFromSessionUUID(
238 + context, pc.getSessionUUID(), "from_notification_status");
239 + newIntent.setAction(Long.toString(System.currentTimeMillis()));
240 +
241 + NotificationCompat.Builder b = new NotificationCompat.Builder(context, CHANNEL_ID);
242 +
243 + if (pc.getNotificationStyle().equalsIgnoreCase(PushCampaign.NOTIFICATION_STYLE_BIG_PICTURE)
244 + && !TextUtils.isEmpty(pc.getBigPictureUrl())) {
245 + //get big picture style
246 + NotificationCompat.BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
247 + bigPictureStyle.bigPicture(loadDrawable(pc.getBigPictureUrl()));
248 + if (!TextUtils.isEmpty(pc.getSubtitle())) {
249 + bigPictureStyle.setSummaryText(pc.getSubtitle());
250 + }
251 + b.setStyle(bigPictureStyle);
252 + } else if (pc.getNotificationStyle().equalsIgnoreCase(PushCampaign.NOTIFICATION_STYLE_BIG_TEXT)
253 + && !TextUtils.isEmpty(pc.getBigText())) {
254 + //get big picture style
255 + NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle();
256 + bigTextStyle.bigText(pc.getBigText());
257 + if (!TextUtils.isEmpty(pc.getSummaryText())) {
258 + bigTextStyle.setSummaryText(pc.getSummaryText());
259 + }
260 +
261 + b.setStyle(bigTextStyle);
262 + } else if (pc.getNotificationStyle().equalsIgnoreCase(PushCampaign.NOTIFICATION_STYLE_INBOX_STYLE)) {
263 + NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
264 + for (int i = 0; i < pc.getInboxLines().size(); i++) {
265 + inboxStyle.addLine(pc.getInboxLines().get(i));
266 + }
267 + if (!TextUtils.isEmpty(pc.getSummaryText())) {
268 + inboxStyle.setSummaryText(pc.getSummaryText());
269 + }
270 + b.setStyle(inboxStyle);
271 + }
272 +
273 + b.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS);
274 + b.setTicker(pc.getTicker());
275 + b.setChannelId(CHANNEL_ID);
276 + b.setContentTitle(pc.getTitle());
277 + b.setContentText(pc.getSubtitle());
278 + b.setSubText(pc.getContent());
279 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
280 + b.setContentIntent(PendingIntent.getActivity(context, uid, newIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE));
281 + } else {
282 + b.setContentIntent(PendingIntent.getActivity(context, uid, newIntent, PendingIntent.FLAG_ONE_SHOT));
283 + }
284 + b.setAutoCancel(true);
285 + b.setOngoing(true);
286 + b.setAutoCancel(true);
287 + b.setSmallIcon(WarplyProperty.getPushIconResId(context));
288 + b.setLargeIcon(loadDrawable(pc.getImageUrl()));
289 +
290 + //Add remote view as content if exists
291 + if (remoteViews != null) {
292 + b.setContent(remoteViews);
293 + }
294 +
295 + if (pc.getSoundUri(context) == null)
296 + b.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));
297 + else
298 + b.setSound(pc.getSoundUri(context));
299 +
300 +
301 + //If push has color
302 + int color = WarplyProperty.getPushColor(context);
303 + if (!TextUtils.isEmpty(pc.getColor())) {
304 + try {
305 + color = Color.parseColor(pc.getColor());
306 + } catch (IllegalArgumentException iae) {
307 + iae.printStackTrace();
308 + }
309 + }
310 + b.setColor(color);
311 +
312 + //add actions
313 + if (actions != null) {
314 + if (actions.size() > 0) {
315 + for (NotificationCompat.Action action : actions) {
316 + b.addAction(action);
317 + }
318 + }
319 + }
320 +
321 + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
322 +
323 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm != null) {
324 + NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, "notification_channel", NotificationManager.IMPORTANCE_DEFAULT);
325 + nm.createNotificationChannel(notificationChannel);
326 + Notification notification_build_with_largeicon = b.setChannelId(CHANNEL_ID).build();
327 + notification_build_with_largeicon.flags = Notification.FLAG_AUTO_CANCEL; //notification with only large icon due to the two build notificaition is not canceled from bar
328 + nm.notify(pc.getSessionUUID(), uid, notification_build_with_largeicon);
329 + } else {
330 + Notification notification_build_with_largeicon = b.build();
331 + notification_build_with_largeicon.flags = Notification.FLAG_AUTO_CANCEL; //notification with only large icon due to the two build notificaition is not canceled from bar
332 + if (nm != null) {
333 + nm.notify(pc.getSessionUUID(), uid, notification_build_with_largeicon);
334 + }
335 + }
336 + }
337 +
338 + /**
339 + * Shows a default notification for the app
340 + */
341 + private void showDefaultNotification(Context context, Bundle data) {
342 + String title = getNotificationTitle(data);
343 + String ticker = getNotificationTicker(data);
344 + String message = getNotificationMessage(data);
345 +
346 + PushCampaign pc = new PushCampaign(data);
347 +
348 + if (pc != null) {
349 + PackageManager pm = context.getPackageManager();
350 + Intent newIntent = pm.getLaunchIntentForPackage(context.getPackageName());
351 +
352 + showNotification(context, pc, newIntent, null, null, null, 0);
353 + }
354 + }
355 +
356 + /**
357 + * Shows a notification with custom parameters
358 + */
359 + private void showNotification(Context context, PushCampaign pc, Intent newIntent, List<NotificationCompat.Action> actions, RemoteViews remoteViews, String tag, int id) {
360 + SecureRandom randomGenerator = new SecureRandom();
361 + int uid = randomGenerator.nextInt(1000);
362 + if (id > 0) {
363 + uid = id;
364 + }
365 + WarpUtils.log("Showing notification with tag: " + tag + " and id: " + id);
366 +
367 + NotificationCompat.Builder b = new NotificationCompat.Builder(context, CHANNEL_ID);
368 +
369 + if (pc.getNotificationStyle().equalsIgnoreCase(PushCampaign.NOTIFICATION_STYLE_BIG_PICTURE)
370 + && !TextUtils.isEmpty(pc.getBigPictureUrl())) {
371 + //get big picture style
372 + NotificationCompat.BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
373 + bigPictureStyle.bigPicture(loadDrawable(pc.getBigPictureUrl()));
374 + if (!TextUtils.isEmpty(pc.getSubtitle())) {
375 + bigPictureStyle.setSummaryText(pc.getSubtitle());
376 + }
377 + b.setStyle(bigPictureStyle);
378 + } else if (pc.getNotificationStyle().equalsIgnoreCase(PushCampaign.NOTIFICATION_STYLE_BIG_TEXT)
379 + && !TextUtils.isEmpty(pc.getBigText())) {
380 + //get big picture style
381 + NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle();
382 + bigTextStyle.bigText(pc.getBigText());
383 + if (!TextUtils.isEmpty(pc.getSummaryText())) {
384 + bigTextStyle.setSummaryText(pc.getSummaryText());
385 + }
386 +
387 + b.setStyle(bigTextStyle);
388 + } else if (pc.getNotificationStyle().equalsIgnoreCase(PushCampaign.NOTIFICATION_STYLE_INBOX_STYLE)) {
389 + NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
390 + for (int i = 0; i < pc.getInboxLines().size(); i++) {
391 + inboxStyle.addLine(pc.getInboxLines().get(i));
392 + }
393 + if (!TextUtils.isEmpty(pc.getSummaryText())) {
394 + inboxStyle.setSummaryText(pc.getSummaryText());
395 + }
396 + b.setStyle(inboxStyle);
397 + }
398 +
399 + b.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS);
400 + b.setTicker(pc.getTicker());
401 + b.setChannelId(CHANNEL_ID);
402 + b.setContentTitle(pc.getTitle());
403 + b.setContentText(pc.getSubtitle());
404 + b.setSubText(pc.getContent());
405 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
406 + b.setContentIntent(PendingIntent.getActivity(context, uid, newIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE));
407 + } else {
408 + b.setContentIntent(PendingIntent.getActivity(context, uid, newIntent, PendingIntent.FLAG_ONE_SHOT));
409 + }
410 + b.setAutoCancel(true);
411 + b.setOngoing(true);
412 + b.setAutoCancel(true);
413 + b.setSmallIcon(WarplyProperty.getPushIconResId(context));
414 + b.setLargeIcon(loadDrawable(pc.getImageUrl()));
415 +
416 + if (remoteViews != null) {
417 + b.setContent(remoteViews);
418 + }
419 +
420 + if (pc.getSoundUri(context) == null)
421 + b.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));
422 + else
423 + b.setSound(pc.getSoundUri(context));
424 +
425 + //If push has color
426 + int color = WarplyProperty.getPushColor(context);
427 + if (!TextUtils.isEmpty(pc.getColor())) {
428 + try {
429 + color = Color.parseColor(pc.getColor());
430 + } catch (IllegalArgumentException iae) {
431 + iae.printStackTrace();
432 + }
433 + }
434 + b.setColor(color);
435 +
436 + //add actions
437 + if (actions != null) {
438 + if (actions.size() > 0) {
439 + for (NotificationCompat.Action action : actions) {
440 + b.addAction(action);
441 + }
442 + }
443 + }
444 +
445 + newIntent.setAction(Long.toString(System.currentTimeMillis()));
446 + PendingIntent pi;
447 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
448 + pi = PendingIntent.getActivity(context, uid, newIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
449 + } else {
450 + pi = PendingIntent.getActivity(context, uid, newIntent, PendingIntent.FLAG_ONE_SHOT);
451 + }
452 + b.setContentIntent(pi);
453 +
454 + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
455 +
456 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm != null) {
457 + NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, "notification_channel", NotificationManager.IMPORTANCE_DEFAULT);
458 + nm.createNotificationChannel(notificationChannel);
459 + Notification notification_build = b.setChannelId(CHANNEL_ID).build();
460 + notification_build.flags = Notification.FLAG_AUTO_CANCEL;
461 + nm.notify(pc.getSessionUUID(), uid, notification_build);
462 + } else {
463 + Notification notification_build = b.build();
464 + notification_build.flags = Notification.FLAG_AUTO_CANCEL;
465 + if (nm != null) {
466 + nm.notify(pc.getSessionUUID(), uid, notification_build);
467 + }
468 + }
469 + }
470 +
471 + // ===========================================================
472 + // Helper Methods
473 + // ===========================================================
474 +
475 + public JSONObject getPayload(Bundle data) {
476 + String payloadStr = data.getString(KEY_PAYLOAD);
477 + try {
478 + JSONObject payload = new JSONObject(payloadStr);
479 + return payload;
480 + } catch (JSONException e) {
481 + WarpUtils.log("Error with payload", e);
482 + return null;
483 + }
484 + }
485 +
486 + public String getNotificationTitle(Bundle data) {
487 + return data.getString(KEY_TITLE);
488 + }
489 +
490 + public String getNotificationMessage(Bundle data) {
491 + return data.getString(KEY_MESSAGE);
492 + }
493 +
494 + public String getNotificationTicker(Bundle data) {
495 + return data.getString(KEY_TICKER);
496 + }
497 +
498 + public String getNotificationSubtitle(Bundle data) {
499 + return data.getString(KEY_SUBTITLE);
500 + }
501 +
502 + private static Bitmap loadDrawable(final String urlString) {
503 + if (urlString == null || TextUtils.isEmpty(urlString))
504 + return null;
505 +
506 + URLThread urlThread = new URLThread(urlString);
507 + try {
508 + urlThread.start();
509 + urlThread.join();
510 + } catch (InterruptedException e) {
511 + e.printStackTrace();
512 + }
513 +
514 + return bFinal;
515 + }
516 +
517 + /**
518 + * Convert a JSONObject to a Bundle
519 + */
520 + public static Bundle jsonToBundle(JSONObject jsonObject) throws JSONException {
521 + Bundle bundle = new Bundle();
522 + Iterator<String> iter = jsonObject.keys();
523 + while (iter.hasNext()) {
524 + String key = iter.next();
525 + String value = jsonObject.getString(key);
526 + bundle.putString(key, value);
527 + }
528 + return bundle;
529 + }
530 +}