Skip to content

Commit a32d109

Browse files
committed
Merge branch 'release/3.0.1'
# Conflicts: # AndroidSDK/src/com/leanplum/internal/Constants.java
2 parents 2750234 + 5e7590c commit a32d109

19 files changed

+573
-86
lines changed

AndroidSDK/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ allprojects {
1717
dependencies {
1818
// Compile dependencies will be added as dependency in pom file.
1919
//noinspection GradleDynamicVersion
20-
compile "com.android.support:support-v4:[14.0.0,${SUPPORT_LIBRARY_VERSION}]"
20+
compile "com.android.support:support-v4:[22.0.0,${SUPPORT_LIBRARY_VERSION}]"
2121
//noinspection GradleDynamicVersion
22-
compile "com.android.support:appcompat-v7:[14.0.0,${SUPPORT_LIBRARY_VERSION}]"
22+
compile "com.android.support:appcompat-v7:[22.0.0,${SUPPORT_LIBRARY_VERSION}]"
2323

2424
// Provided dependencies are optional dependencies and will not show up in pom file.
2525
provided('com.google.android.gms:play-services-gcm:[8.3.0,)') {

AndroidSDK/src/com/leanplum/ActionContext.java

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,8 @@ public void runActionNamed(String name) {
374374
/**
375375
* Return true if here was an action for this message and we started it.
376376
*/
377-
private boolean createActionContextForMessageId(String messageAction, Map<String, Object>
378-
messageArgs, String messageId, String name, Boolean chained) {
377+
private boolean createActionContextForMessageId(String messageAction,
378+
Map<String, Object> messageArgs, String messageId, String name, Boolean chained) {
379379
try {
380380
final ActionContext actionContext = new ActionContext(messageAction,
381381
messageArgs, messageId);
@@ -406,28 +406,69 @@ public void variablesChanged() {
406406
}
407407

408408
/**
409-
* Return true if here was action "Chain to Existing Message" and we started it.
409+
* Return true if there was action "Chain to Existing Message" and we started it.
410410
*/
411-
private boolean isChainToExistingMessageStarted(Map<String, Object> args, String name) {
411+
private boolean isChainToExistingMessageStarted(Map<String, Object> args, final String name) {
412412
if (args == null) {
413413
return false;
414414
}
415415

416-
String messageId = (String) args.get(Constants.Values.CHAIN_MESSAGE_ARG);
417-
Object actionType = args.get(Constants.Values.ACTION_ARG);
418-
if (messageId != null && Constants.Values.CHAIN_MESSAGE_ACTION_NAME.equals(actionType)) {
419-
Map<String, Object> messages = VarCache.messages();
420-
if (messages != null && messages.containsKey(messageId)) {
421-
Map<String, Object> message = CollectionUtil.uncheckedCast(messages.get(messageId));
422-
if (message != null) {
423-
Map<String, Object> messageArgs = CollectionUtil.uncheckedCast(
424-
message.get(Constants.Keys.VARS));
425-
Object messageAction = message.get("action");
426-
return messageAction != null && createActionContextForMessageId(messageAction.toString(),
427-
messageArgs, messageId, name, true);
416+
final String messageId = getChainedMessageId(args);
417+
if (!shouldForceContentUpdateForChainedMessage(args)) {
418+
return executeChainedMessage(messageId, VarCache.messages(), name);
419+
} else {
420+
// message may not on the device yet, so we need to fetch it.
421+
Leanplum.forceContentUpdate(new VariablesChangedCallback() {
422+
@Override
423+
public void variablesChanged() {
424+
executeChainedMessage(messageId, VarCache.messages(), name);
428425
}
426+
});
427+
}
428+
return false;
429+
}
430+
431+
/**
432+
* Return true if there is a chained message in the actionMap that is not yet loaded onto the device.
433+
*/
434+
static boolean shouldForceContentUpdateForChainedMessage(Map<String, Object> actionMap) {
435+
if (actionMap == null) {
436+
return false;
437+
}
438+
String chainedMessageId = getChainedMessageId(actionMap);
439+
if (chainedMessageId != null
440+
&& (VarCache.messages() == null || !VarCache.messages().containsKey(chainedMessageId))) {
441+
return true;
442+
}
443+
return false;
444+
}
445+
446+
/**
447+
* Extract chained messageId from parent message's actionMap. If it doesn't exist then return null.
448+
*/
449+
static String getChainedMessageId(Map<String, Object> actionMap) {
450+
if (actionMap != null) {
451+
if (Constants.Values.CHAIN_MESSAGE_ACTION_NAME.equals(actionMap.get(Constants.Values.ACTION_ARG))) {
452+
return (String) actionMap.get(Constants.Values.CHAIN_MESSAGE_ARG);
429453
}
430454
}
455+
return null;
456+
}
457+
458+
459+
private boolean executeChainedMessage(String messageId, Map<String, Object> messages,
460+
String name) {
461+
if (messages == null) {
462+
return false;
463+
}
464+
Map<String, Object> message = CollectionUtil.uncheckedCast(messages.get(messageId));
465+
if (message != null) {
466+
Map<String, Object> messageArgs = CollectionUtil.uncheckedCast(
467+
message.get(Constants.Keys.VARS));
468+
Object messageAction = message.get("action");
469+
return messageAction != null && createActionContextForMessageId(messageAction.toString(),
470+
messageArgs, messageId, name, true);
471+
}
431472
return false;
432473
}
433474

AndroidSDK/src/com/leanplum/Leanplum.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,7 @@ public void onResponse(boolean success) {
821821
public void run() {
822822
try {
823823
NotificationCompat.Builder builder =
824-
LeanplumNotificationHelper.getDefaultNotificationBuilder(context,
824+
LeanplumNotificationHelper.getDefaultCompatNotificationBuilder(context,
825825
BuildUtil.isNotificationChannelSupported(context));
826826
if (builder == null) {
827827
return;

AndroidSDK/src/com/leanplum/LeanplumInboxMessage.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import com.leanplum.internal.CollectionUtil;
2828
import com.leanplum.internal.Constants;
29+
import com.leanplum.internal.JsonConverter;
2930
import com.leanplum.internal.Log;
3031
import com.leanplum.internal.Request;
3132
import com.leanplum.internal.Util;
@@ -110,11 +111,10 @@ public Uri getImageUrl() {
110111
public JSONObject getData() {
111112
JSONObject object = null;
112113
try {
113-
String dataString = getContext().stringNamed(Constants.Keys.DATA);
114-
if (!TextUtils.isEmpty(dataString)) {
115-
object = new JSONObject(dataString);
116-
}
117-
} catch (Exception e) {
114+
Map<String, ?> mapData =
115+
CollectionUtil.uncheckedCast(getContext().objectNamed(Constants.Keys.DATA));
116+
object = JsonConverter.mapToJsonObject(mapData);
117+
} catch (Throwable t) {
118118
Log.w("Unable to parse JSONObject for Data field of inbox message.");
119119
}
120120
return object;
@@ -127,7 +127,6 @@ public String getMessageId() {
127127
return messageId;
128128
}
129129

130-
131130
/**
132131
* Returns the title of the inbox message.
133132
*/
@@ -293,5 +292,4 @@ private Map<String, Object> actionArgs() {
293292
private void setIsRead(boolean isRead) {
294293
this.isRead = isRead;
295294
}
296-
297295
}

AndroidSDK/src/com/leanplum/LeanplumNotificationHelper.java

Lines changed: 148 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,19 @@
2020
*/
2121
package com.leanplum;
2222

23+
import android.app.Notification;
24+
import android.app.PendingIntent;
2325
import android.content.Context;
26+
import android.content.res.Resources;
27+
import android.graphics.Bitmap;
28+
import android.os.Build;
2429
import android.os.Bundle;
2530
import android.support.v4.app.NotificationCompat;
2631
import android.text.TextUtils;
32+
import android.util.TypedValue;
33+
import android.widget.RemoteViews;
2734

35+
import com.leanplum.internal.Constants;
2836
import com.leanplum.internal.JsonConverter;
2937
import com.leanplum.internal.Log;
3038
import com.leanplum.utils.BuildUtil;
@@ -37,6 +45,10 @@
3745
* @author Anna Orlova
3846
*/
3947
class LeanplumNotificationHelper {
48+
49+
private static final int BIGPICTURE_TEXT_TOP_PADDING = -14;
50+
private static final int BIGPICTURE_TEXT_SIZE = 14;
51+
4052
/**
4153
* If notification channels are supported this method will try to create
4254
* NotificationCompat.Builder with default notification channel if default channel id is provided.
@@ -49,7 +61,7 @@ class LeanplumNotificationHelper {
4961
*/
5062
// NotificationCompat.Builder(Context context) constructor was deprecated in API level 26.
5163
@SuppressWarnings("deprecation")
52-
static NotificationCompat.Builder getDefaultNotificationBuilder(Context context,
64+
static NotificationCompat.Builder getDefaultCompatNotificationBuilder(Context context,
5365
boolean isNotificationChannelSupported) {
5466
if (!isNotificationChannelSupported) {
5567
return new NotificationCompat.Builder(context);
@@ -63,6 +75,32 @@ static NotificationCompat.Builder getDefaultNotificationBuilder(Context context,
6375
}
6476
}
6577

78+
/**
79+
* If notification channels are supported this method will try to create
80+
* Notification.Builder with default notification channel if default channel id is provided.
81+
* If notification channels not supported this method will return Notification.Builder for
82+
* context.
83+
*
84+
* @param context The application context.
85+
* @param isNotificationChannelSupported True if notification channels are supported.
86+
* @return Notification.Builder for provided context or null.
87+
*/
88+
// Notification.Builder(Context context) constructor was deprecated in API level 26.
89+
@SuppressWarnings("deprecation")
90+
private static Notification.Builder getDefaultNotificationBuilder(Context context,
91+
boolean isNotificationChannelSupported) {
92+
if (!isNotificationChannelSupported) {
93+
return new Notification.Builder(context);
94+
}
95+
String channelId = LeanplumNotificationChannel.getDefaultNotificationChannelId(context);
96+
if (!TextUtils.isEmpty(channelId)) {
97+
return new Notification.Builder(context, channelId);
98+
} else {
99+
Log.w("Failed to post notification, there are no notification channels configured.");
100+
return null;
101+
}
102+
}
103+
66104
/**
67105
* If notification channels are supported this method will try to create a channel with
68106
* information from the message if it doesn't exist and return NotificationCompat.Builder for this
@@ -76,7 +114,7 @@ static NotificationCompat.Builder getDefaultNotificationBuilder(Context context,
76114
*/
77115
// NotificationCompat.Builder(Context context) constructor was deprecated in API level 26.
78116
@SuppressWarnings("deprecation")
79-
static NotificationCompat.Builder getNotificationBuilder(Context context, Bundle message) {
117+
static NotificationCompat.Builder getNotificationCompatBuilder(Context context, Bundle message) {
80118
NotificationCompat.Builder builder = null;
81119
// If we are targeting API 26, try to find supplied channel to post notification.
82120
if (BuildUtil.isNotificationChannelSupported(context)) {
@@ -94,7 +132,7 @@ static NotificationCompat.Builder getNotificationBuilder(Context context, Bundle
94132
}
95133
} else {
96134
// If channel isn't supplied, try to look up for default channel.
97-
builder = LeanplumNotificationHelper.getDefaultNotificationBuilder(context, true);
135+
builder = LeanplumNotificationHelper.getDefaultCompatNotificationBuilder(context, true);
98136
}
99137
} catch (Exception e) {
100138
Log.e("Failed to post notification to specified channel.");
@@ -104,4 +142,111 @@ static NotificationCompat.Builder getNotificationBuilder(Context context, Bundle
104142
}
105143
return builder;
106144
}
145+
146+
/**
147+
* If notification channels are supported this method will try to create a channel with
148+
* information from the message if it doesn't exist and return Notification.Builder for this
149+
* channel. In the case where no channel information inside the message, we will try to get a
150+
* channel with default channel id. If notification channels not supported this method will return
151+
* Notification.Builder for context.
152+
*
153+
* @param context The application context.
154+
* @param message Push notification Bundle.
155+
* @return Notification.Builder or null.
156+
*/
157+
static Notification.Builder getNotificationBuilder(Context context, Bundle message) {
158+
Notification.Builder builder = null;
159+
// If we are targeting API 26, try to find supplied channel to post notification.
160+
if (BuildUtil.isNotificationChannelSupported(context)) {
161+
try {
162+
String channel = message.getString("lp_channel");
163+
if (!TextUtils.isEmpty(channel)) {
164+
// Create channel if it doesn't exist and post notification to that channel.
165+
Map<String, Object> channelDetails = JsonConverter.fromJson(channel);
166+
String channelId = LeanplumNotificationChannel.createNotificationChannel(context,
167+
channelDetails);
168+
if (!TextUtils.isEmpty(channelId)) {
169+
builder = new Notification.Builder(context, channelId);
170+
} else {
171+
Log.w("Failed to post notification to specified channel.");
172+
}
173+
} else {
174+
// If channel isn't supplied, try to look up for default channel.
175+
builder = LeanplumNotificationHelper.getDefaultNotificationBuilder(context, true);
176+
}
177+
} catch (Exception e) {
178+
Log.e("Failed to post notification to specified channel.");
179+
}
180+
} else {
181+
builder = new Notification.Builder(context);
182+
}
183+
return builder;
184+
}
185+
186+
/**
187+
* Gets Notification.Builder with 2 lines at BigPictureStyle notification text.
188+
*
189+
* @param context The application context.
190+
* @param message Push notification Bundle.
191+
* @param contentIntent PendingIntent.
192+
* @param title String with title for push notification.
193+
* @param messageText String with text for push notification.
194+
* @param bigPicture Bitmap for BigPictureStyle notification.
195+
* @return Notification.Builder or null.
196+
*/
197+
static Notification.Builder getNotificationBuilder(Context context, Bundle message,
198+
PendingIntent contentIntent, String title, final String messageText, Bitmap bigPicture) {
199+
if (Build.VERSION.SDK_INT < 16) {
200+
return null;
201+
}
202+
Notification.Builder notificationBuilder =
203+
getNotificationBuilder(context, message);
204+
notificationBuilder.setSmallIcon(context.getApplicationInfo().icon)
205+
.setContentTitle(title)
206+
.setContentText(messageText);
207+
Notification.BigPictureStyle bigPictureStyle = new Notification.BigPictureStyle() {
208+
@Override
209+
protected RemoteViews getStandardView(int layoutId) {
210+
RemoteViews remoteViews = super.getStandardView(layoutId);
211+
// Modifications of stanxdard push RemoteView.
212+
try {
213+
int id = Resources.getSystem().getIdentifier("text", "id", "android");
214+
remoteViews.setBoolean(id, "setSingleLine", false);
215+
remoteViews.setInt(id, "setLines", 2);
216+
if (Build.VERSION.SDK_INT < 23) {
217+
// Make text smaller.
218+
remoteViews.setViewPadding(id, 0, BIGPICTURE_TEXT_TOP_PADDING, 0, 0);
219+
remoteViews.setTextViewTextSize(id, TypedValue.COMPLEX_UNIT_SP, BIGPICTURE_TEXT_SIZE);
220+
}
221+
} catch (Throwable throwable) {
222+
Log.e("Cannot modify push notification layout.");
223+
}
224+
return remoteViews;
225+
}
226+
};
227+
228+
bigPictureStyle.bigPicture(bigPicture)
229+
.setBigContentTitle(title)
230+
.setSummaryText(message.getString(Constants.Keys.PUSH_MESSAGE_TEXT));
231+
notificationBuilder.setStyle(bigPictureStyle);
232+
233+
if (Build.VERSION.SDK_INT >= 24) {
234+
// By default we cannot reach getStandardView method on API>=24. I we call
235+
// createBigContentView, Android will call getStandardView method and we can get
236+
// modified RemoteView.
237+
try {
238+
RemoteViews remoteView = notificationBuilder.createBigContentView();
239+
if (remoteView != null) {
240+
// We need to set received RemoteView as a custom big content view.
241+
notificationBuilder.setCustomBigContentView(remoteView);
242+
}
243+
} catch (Throwable t) {
244+
Log.e("Cannot modify push notification layout.", t);
245+
}
246+
}
247+
248+
notificationBuilder.setAutoCancel(true);
249+
notificationBuilder.setContentIntent(contentIntent);
250+
return notificationBuilder;
251+
}
107252
}

0 commit comments

Comments
 (0)