Skip to content

Commit d478fe8

Browse files
authored
IAM Handlers refactoring (#503)
* Refactoring initial commit * Improvements: - fix several TODOs in the code - remove messageDisplayedHandlers - remove onAction listeners * Run push open action with priority also other improvements and fixes * Remove actionNamedHandler * Fix dismiss of inapps when activity is changed. Add localCaps check. More details: - Queue is paused during activity switching, i.e. when activity is paused and resumed the queue will also gets paused or resumed. - When activity is switched (notification click) the current presented in-app dialog must be closed to avoid leaking of Window objects. - Android OS works like that: * If you have activity A1 and user is switching to another activity A2 then A1.onPause is invoked first, then A2.onResume and finally A1.stop will be called. * If you have activity A1 shown and you bring app to background it will call A1.onPause and A1.onStop freeing our LeanplumActivityHelper.currentActivity reference, but we need to somehow keep reference to the last activity to free any Window objects. - For the above reasons there is a change in LeanplumActivityHelper.onResume calling avoidWindowLeaks method. It works with lastForegroundActivity reference, that keeps the previous activity instance. It will be freed when onDestroy is called for the same activity (check LeanplumActivityHelper.onDestroy). - Changes are tested with both Legacy messaging push and Campaign push, both containing "open app", "deeplink" or "iam" payload. Tested with Android 11 and Android 12, because of the notification trampolines. - There is only one issue, or a feature, however you like it - If you have inapp M1 presented, M2 waiting in the queue, and a push P1 comes with payload M3. What will happen on a P1 click is first M1 dismisses, because activity changes (depends on implementation), next M2 is presented, but the M3 from payload is added with higher priority so M2 is dismissed too and only M3 is presented. If you have a third inapp it will remain in the queue and will present after M3. * Change orderMessages to prioritizeMessages. Pause action queue on dev device message. * Remove redundant deprecations * Guard against user exception * Fix some TODOs and improve javadoc * Remove resolved TODOs * Move some of the files to .internal package * Add javadoc and convert java to kotlin * Fix unit tests. Remove couple unnecessary * Fix merge error * Add setPaused and setEnabled to LeanplumActions. Fix a pause bug. * Add MessageDisplayChoice.delayIndefinitely() * Fix some of the review comments * Remove functionality for ignored activites. Replaced by MessageDisplayController.shouldDisplayMessage. * Remove the deprecated jcenter() repository * Fix runActionNamed to not call actionDismissed. Tested all in-app templates with legacy messaging and campaigns. * Add JvmStatic for the new static methods * Fix review comments about queue paused state * Allow nullable controller and listener * IAM Handlers unit tests (#504) * Add tests for queue and definitions * Add setPaused and setEnabled to LeanplumActions. Fix a pause bug. * Add tests for ActionManagerTriggering * Add MessageDisplayChoice.delayIndefinitely() * Add tests for ActionManagerExecution * Add test for MessageDisplayListener. Improve readability.
1 parent 6481112 commit d478fe8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2131
-854
lines changed

AndroidSDKCore/src/main/java/com/leanplum/ActionArgs.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public ActionArgs withAction(String name, String defaultValue) {
116116
return this;
117117
}
118118

119-
List<ActionArg<?>> getValue() {
119+
public List<ActionArg<?>> getValue() {
120120
return args;
121121
}
122122

AndroidSDKCore/src/main/java/com/leanplum/ActionContext.java

Lines changed: 120 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021, Leanplum, Inc. All rights reserved.
2+
* Copyright 2022, Leanplum, Inc. All rights reserved.
33
*
44
* Licensed to the Apache Software Foundation (ASF) under one
55
* or more contributor license agreements. See the NOTICE file
@@ -24,8 +24,11 @@
2424
import androidx.annotation.NonNull;
2525
import android.text.TextUtils;
2626

27-
import com.leanplum.callbacks.ActionCallback;
28-
import com.leanplum.callbacks.VariablesChangedCallback;
27+
import com.leanplum.actions.internal.ActionDidExecute;
28+
import com.leanplum.actions.internal.ActionManagerDefinitionKt;
29+
import com.leanplum.actions.internal.ActionManagerTriggeringKt;
30+
import com.leanplum.actions.internal.ActionDidDismiss;
31+
import com.leanplum.actions.internal.Priority;
2932
import com.leanplum.internal.ActionManager;
3033
import com.leanplum.internal.BaseActionContext;
3134
import com.leanplum.internal.CollectionUtil;
@@ -57,7 +60,9 @@ public class ActionContext extends BaseActionContext implements Comparable<Actio
5760
private String key;
5861
private boolean preventRealtimeUpdating = false;
5962
private ContextualValues contextualValues;
60-
private ActionCallback actionNamedHandler;
63+
private ActionDidDismiss actionDidDismiss;
64+
private ActionDidExecute actionDidExecute;
65+
private boolean isChainedMessage;
6166

6267
public static class ContextualValues {
6368
/**
@@ -106,29 +111,25 @@ public ContextualValues getContextualValues() {
106111
return contextualValues;
107112
}
108113

109-
private static Map<String, Object> getDefinition(String name) {
110-
Map<String, Object> definition = CollectionUtil.uncheckedCast(
111-
VarCache.actionDefinitions().get(name));
114+
private Map<String, Object> getDefinition(String actionName) {
115+
Map<String, Object> definition =
116+
ActionManagerDefinitionKt.getActionDefinitionMap(ActionManager.getInstance(), actionName);
112117
if (definition == null) {
113118
return new HashMap<>();
114119
}
115120
return definition;
116121
}
117122

118-
private Map<String, Object> getDefinition() {
119-
return getDefinition(name);
120-
}
121-
122123
private Map<String, Object> defaultValues() {
123-
Map<String, Object> values = CollectionUtil.uncheckedCast(getDefinition().get("values"));
124+
Map<String, Object> values = CollectionUtil.uncheckedCast(getDefinition(name).get("values"));
124125
if (values == null) {
125126
return new HashMap<>();
126127
}
127128
return values;
128129
}
129130

130131
private Map<String, String> kinds() {
131-
Map<String, String> kinds = CollectionUtil.uncheckedCast(getDefinition().get("kinds"));
132+
Map<String, String> kinds = CollectionUtil.uncheckedCast(getDefinition(name).get("kinds"));
132133
if (kinds == null) {
133134
return new HashMap<>();
134135
}
@@ -352,121 +353,100 @@ private Map<String, Object> getChildArgs(String name) {
352353
return actionArgs;
353354
}
354355

355-
public void setActionNamedHandler(ActionCallback handler) {
356-
actionNamedHandler = handler;
357-
}
358-
359-
private void triggerActionNamedHandler(String name, Map<String, Object> args) {
360-
if (actionNamedHandler != null) {
361-
ActionContext actionContext = new ActionContext(name, args, messageId);
362-
actionContext.parentContext = this;
363-
actionNamedHandler.onResponse(actionContext);
364-
}
365-
}
366-
367356
public void runActionNamed(String name) {
368357
if (TextUtils.isEmpty(name)) {
369358
Log.e("runActionNamed - Invalid name parameter provided.");
370359
return;
371360
}
372361
Map<String, Object> args = getChildArgs(name);
373362

374-
triggerActionNamedHandler(name, args);
363+
if (actionDidExecute != null) {
364+
ActionContext actionNamedContext = new ActionContext(name, args, messageId);
365+
actionNamedContext.parentContext = this;
366+
actionDidExecute.onActionExecuted(actionNamedContext);
367+
}
375368

376369
if (args == null) {
377370
return;
378371
}
379372

380-
// Checks if action "Chain to Existing Message" started.
381-
if (!isChainToExistingMessageStarted(args, name)) {
382-
// Try to start action "Chain to a new Message".
373+
performChainedAction(name, args);
374+
}
375+
376+
private void performChainedAction(String name, Map<String, Object> args) {
377+
String chainedMessageId = getChainedMessageId(args);
378+
379+
if (chainedMessageId != null) { // Chain to existing message
380+
if (shouldFetchChainedMessage(args)) {
381+
boolean previousPauseState = ActionManager.getInstance().isPaused();
382+
ActionManager.getInstance().setPaused(true);
383+
// Message doesn't seem to be on the device,
384+
// so let's forceContentUpdate and retry showing it.
385+
Leanplum.forceContentUpdate(success -> {
386+
if (success) {
387+
executeChainedMessage(chainedMessageId, name);
388+
}
389+
ActionManager.getInstance().setPaused(previousPauseState); // will resume queue
390+
});
391+
} else {
392+
executeChainedMessage(chainedMessageId, name);
393+
// queue will be resumed from onDidDismiss callback in case executeChainedMessage fails
394+
}
395+
396+
} else { // Chain to embedded message
383397
Object messageAction = args.get(Constants.Values.ACTION_ARG);
384398
if (messageAction != null) {
385-
createActionContextForMessageId(messageAction.toString(), args, messageId, name, false);
399+
ActionContext embeddedContext = createChainedContext(
400+
messageAction.toString(),
401+
args,
402+
messageId,
403+
name);
404+
ActionManagerTriggeringKt.trigger(
405+
ActionManager.getInstance(),
406+
embeddedContext,
407+
Priority.HIGH);
386408
}
387409
}
388410
}
389411

390-
/**
391-
* Return true if here was an action for this message and we started it.
392-
*/
393-
private boolean createActionContextForMessageId(String messageAction,
394-
Map<String, Object> messageArgs, String messageId, String name, Boolean chained) {
395-
try {
396-
final ActionContext actionContext = new ActionContext(messageAction,
397-
messageArgs, messageId);
412+
private ActionContext createChainedContext(
413+
String messageAction,
414+
Map<String, Object> messageArgs,
415+
String messageId,
416+
String name) {
417+
418+
ActionContext actionContext = new ActionContext(messageAction, messageArgs, messageId);
398419
actionContext.contextualValues = contextualValues;
399420
actionContext.preventRealtimeUpdating = preventRealtimeUpdating;
400421
actionContext.isRooted = isRooted;
401422
actionContext.key = name;
402-
if (chained) {
403-
LeanplumInternal.triggerAction(actionContext, new VariablesChangedCallback() {
404-
@Override
405-
public void variablesChanged() {
406-
try {
407-
// We do not want to count occurrences for action kind, because in multi message
408-
// campaigns the Open URL action is not a message. Also if the user has defined
409-
// actions of type Action we do not want to count them.
410-
int actionKind = VarCache.getActionDefinitionType(actionContext.name);
411-
if (actionKind == Leanplum.ACTION_KIND_ACTION) {
412-
ActionManager.getInstance().recordChainedActionImpression(messageId);
413-
} else {
414-
ActionManager.getInstance().recordMessageImpression(messageId);
415-
}
416-
Leanplum.triggerMessageDisplayed(actionContext);
417-
418-
} catch (Throwable t) {
419-
Log.exception(t);
420-
}
421-
}
422-
});
423-
} else {
424-
actionContext.parentContext = this;
425-
LeanplumInternal.triggerAction(actionContext);
426-
}
427-
return true;
428-
} catch (Throwable t) {
429-
Log.exception(t);
430-
}
431-
return false;
423+
actionContext.parentContext = this;
424+
return actionContext;
432425
}
433426

434-
/**
435-
* Return true if there was action "Chain to Existing Message" and we started it.
436-
*/
437-
private boolean isChainToExistingMessageStarted(Map<String, Object> args, final String name) {
438-
if (args == null) {
427+
public static boolean shouldFetchChainedMessage(
428+
@NonNull ActionContext context,
429+
String actionName) {
430+
if (TextUtils.isEmpty(actionName)) {
439431
return false;
440432
}
441-
442-
final String messageId = getChainedMessageId(args);
443-
if (!shouldForceContentUpdateForChainedMessage(args)) {
444-
return executeChainedMessage(messageId, VarCache.messages(), name);
445-
} else {
446-
// message may not on the device yet, so we need to fetch it.
447-
Leanplum.forceContentUpdate(new VariablesChangedCallback() {
448-
@Override
449-
public void variablesChanged() {
450-
executeChainedMessage(messageId, VarCache.messages(), name);
451-
}
452-
});
453-
}
454-
return false;
433+
Map<String, Object> args = context.getChildArgs(actionName);
434+
return shouldFetchChainedMessage(args);
455435
}
456436

457437
/**
458438
* Return true if there is a chained message in the actionMap that is not yet loaded onto the device.
459439
*/
460-
static boolean shouldForceContentUpdateForChainedMessage(Map<String, Object> actionMap) {
440+
static boolean shouldFetchChainedMessage(Map<String, Object> actionMap) {
461441
if (actionMap == null) {
462442
return false;
463443
}
464444
String chainedMessageId = getChainedMessageId(actionMap);
465-
if (chainedMessageId != null
466-
&& (VarCache.messages() == null || !VarCache.messages().containsKey(chainedMessageId))) {
467-
return true;
445+
if (chainedMessageId == null) {
446+
return false;
468447
}
469-
return false;
448+
return VarCache.messages() == null
449+
|| !VarCache.messages().containsKey(chainedMessageId);
470450
}
471451

472452
/**
@@ -482,8 +462,8 @@ static String getChainedMessageId(Map<String, Object> actionMap) {
482462
}
483463

484464

485-
private boolean executeChainedMessage(String messageId, Map<String, Object> messages,
486-
String name) {
465+
private boolean executeChainedMessage(String messageId, String name) {
466+
Map<String, Object> messages = VarCache.messages();
487467
if (messages == null) {
488468
return false;
489469
}
@@ -492,8 +472,20 @@ private boolean executeChainedMessage(String messageId, Map<String, Object> mess
492472
Map<String, Object> messageArgs = CollectionUtil.uncheckedCast(
493473
message.get(Constants.Keys.VARS));
494474
Object messageAction = message.get("action");
495-
return messageAction != null && createActionContextForMessageId(messageAction.toString(),
496-
messageArgs, messageId, name, true);
475+
476+
if (messageAction != null) {
477+
ActionContext chainContext = createChainedContext(
478+
messageAction.toString(),
479+
messageArgs,
480+
messageId,
481+
name);
482+
chainContext.isChainedMessage = true;
483+
ActionManagerTriggeringKt.trigger(
484+
ActionManager.getInstance(),
485+
chainContext,
486+
Priority.HIGH);
487+
return true;
488+
}
497489
}
498490
return false;
499491
}
@@ -633,4 +625,36 @@ public static <T> Map<String, T> mapFromJson(JSONObject jsonObject) throws JSONE
633625
public ActionContext getParentContext() {
634626
return parentContext;
635627
}
628+
629+
public void setParentContext(ActionContext parentContext) {
630+
this.parentContext = parentContext;
631+
}
632+
633+
public boolean isChainedMessage() {
634+
return isChainedMessage;
635+
}
636+
637+
@Override
638+
@NonNull
639+
public String toString() {
640+
String parent = "";
641+
if (parentContext != null) {
642+
parent = "(parent=" + parentContext + ")";
643+
}
644+
return name + ":" + messageId + parent;
645+
}
646+
647+
public void actionDismissed() {
648+
if (actionDidDismiss != null) {
649+
actionDidDismiss.onDismiss();
650+
}
651+
}
652+
653+
public void setActionDidDismiss(ActionDidDismiss actionDidDismiss) {
654+
this.actionDidDismiss = actionDidDismiss;
655+
}
656+
657+
public void setActionDidExecute(ActionDidExecute actionDidExecute) {
658+
this.actionDidExecute = actionDidExecute;
659+
}
636660
}

0 commit comments

Comments
 (0)