InAppAutomation extends AirshipComponent implements InAppAutomationScheduler { @WorkerThread private void onPrepareSchedule(final @NonNull Schedule<? extends ScheduleData> schedule, final @Nullable TriggerContext triggerContext, final @NonNull AutomationDriver.PrepareScheduleCallback callback) { Logger.verbose("InAppAutomation - onPrepareSchedule schedule: %s, trigger context: %s", schedule.getId(), triggerContext); if (isScheduleInvalid(schedule)) { backgroundHandler.post(new Runnable() { @Override public void run() { if (remoteDataSubscriber.isUpToDate()) { callback.onFinish(AutomationDriver.PREPARE_RESULT_INVALIDATE); } else { remoteDataSubscriber.addListener(new InAppRemoteDataObserver.Listener() { @Override public void onSchedulesUpdated() { callback.onFinish(AutomationDriver.PREPARE_RESULT_INVALIDATE); remoteDataSubscriber.removeListener(this); } }); } } }); return; } RetryingExecutor.Operation checkAudience = new RetryingExecutor.Operation() { @Override public int run() { Map<String, Set<String>> tagGroups = null; if (schedule.getAudience() == null) { return RetryingExecutor.RESULT_FINISHED; } if (schedule.getAudience().getTagSelector() != null && schedule.getAudience().getTagSelector().containsTagGroups()) { Map<String, Set<String>> tags = schedule.getAudience().getTagSelector().getTagGroups(); TagGroupResult result = audienceManager.getTags(tags); if (!result.success) { return RetryingExecutor.RESULT_RETRY; } tagGroups = result.tagGroups; } if (AudienceChecks.checkAudience(getContext(), schedule.getAudience(), tagGroups)) { return RetryingExecutor.RESULT_FINISHED; } callback.onFinish(getPrepareResultMissedAudience(schedule)); return RetryingExecutor.RESULT_CANCEL; } }; RetryingExecutor.Operation prepareSchedule = new RetryingExecutor.Operation() { @Override public int run() { switch (schedule.getType()) { case Schedule.TYPE_ACTION: callback.onFinish(AutomationDriver.PREPARE_RESULT_CONTINUE); return RetryingExecutor.RESULT_FINISHED; case Schedule.TYPE_IN_APP_MESSAGE: inAppMessageManager.onPrepare(schedule.getId(), (InAppMessage) schedule.coerceType(), callback); return RetryingExecutor.RESULT_FINISHED; case Schedule.TYPE_DEFERRED: return onPrepareDeferredSchedule(schedule, triggerContext, callback); } Logger.error("Unexpected schedule type: %s", schedule.getType()); return RetryingExecutor.RESULT_FINISHED; } }; retryingExecutor.execute(checkAudience, prepareSchedule); } @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)  InAppAutomation(@NonNull Context context,
                           @NonNull PreferenceDataStore preferenceDataStore,
                           @NonNull AirshipRuntimeConfig runtimeConfig,
                           @NonNull Analytics analytics,
                           @NonNull RemoteData remoteData,
                           @NonNull AirshipChannel airshipChannel,
                           @NonNull NamedUser namedUser); @VisibleForTesting  InAppAutomation(@NonNull Context context,
                    @NonNull PreferenceDataStore preferenceDataStore,
                    @NonNull AutomationEngine engine,
                    @NonNull AirshipChannel airshipChannel,
                    @NonNull AudienceManager audienceManager,
                    @NonNull InAppRemoteDataObserver observer,
                    @NonNull InAppMessageManager inAppMessageManager,
                    @NonNull RetryingExecutor retryingExecutor,
                    @NonNull ActionRunRequestFactory actionRunRequestFactory,
                    @NonNull DeferredScheduleClient deferredScheduleClient); @NonNull static InAppAutomation shared(); @Override @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @AirshipComponentGroups.Group int getComponentGroup(); @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Override void onAirshipReady(@NonNull UAirship airship); @Override void onNewConfig(@Nullable JsonMap configValue); InAppMessageManager getInAppMessageManager(); @NonNull @Override PendingResult<Boolean> schedule(@NonNull List<Schedule<? extends ScheduleData>> schedules); @NonNull @Override PendingResult<Boolean> schedule(@NonNull Schedule<? extends ScheduleData> schedule); @NonNull PendingResult<Boolean> cancelSchedule(@NonNull String scheduleId); @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) PendingResult<Boolean> cancelSchedules(@NonNull @Schedule.Type String type); @NonNull PendingResult<Boolean> cancelScheduleGroup(@NonNull String group); @NonNull @Override PendingResult<Collection<Schedule<Actions>>> getActionScheduleGroup(@NonNull final String group); @NonNull @Override PendingResult<Schedule<Actions>> getActionSchedule(@NonNull String scheduleId); @NonNull @Override PendingResult<Collection<Schedule<Actions>>> getActionSchedules(); @NonNull @Override PendingResult<Collection<Schedule<InAppMessage>>> getMessageScheduleGroup(@NonNull String group); @NonNull @Override PendingResult<Schedule<InAppMessage>> getMessageSchedule(@NonNull String scheduleId); @NonNull @Override PendingResult<Collection<Schedule<InAppMessage>>> getMessageSchedules(); @NonNull @Override @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) PendingResult<Collection<Schedule<? extends ScheduleData>>> getSchedules(); @NonNull @Override PendingResult<Boolean> editSchedule(@NonNull String scheduleId, @NonNull ScheduleEdits<? extends ScheduleData> edits); void setPaused(boolean paused); boolean isPaused(); void setEnabled(boolean enabled); boolean isEnabled();  }
@Test public void testPrepareSchedule() { InAppMessage message = InAppMessage.newBuilder() .setDisplayContent(new CustomDisplayContent(JsonValue.NULL)) .build(); Schedule<InAppMessage> schedule = Schedule.newBuilder(message) .addTrigger(Triggers.newAppInitTriggerBuilder().setGoal(1).build()) .build(); AutomationDriver.PrepareScheduleCallback callback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, callback); verify(mockIamManager).onPrepare(schedule.getId(), schedule.getData(), callback); }
@Test public void testPrepareDeferredSchedule() throws MalformedURLException, AuthException, RequestException { when(mockChannel.getId()).thenReturn("some channel"); CustomEvent event = CustomEvent.newBuilder("some event").build(); TriggerContext triggerContext = new TriggerContext(Triggers.newCustomEventTriggerBuilder().build(), event.toJsonValue()); Deferred deferredScheduleData = new Deferred(new URL("https: Schedule<? extends ScheduleData> schedule = Schedule.newBuilder(deferredScheduleData) .addTrigger(Triggers.newCustomEventTriggerBuilder().build()) .build(); InAppMessage message = InAppMessage.newBuilder() .setDisplayContent(new CustomDisplayContent(JsonValue.NULL)) .build(); List<TagGroupsMutation> tagOverrides = new ArrayList<>(); tagOverrides.add(TagGroupsMutation.newRemoveTagsMutation("foo", tagSet("one", "two"))); tagOverrides.add(TagGroupsMutation.newSetTagsMutation("bar", tagSet("a"))); when(mockAudienceManager.getTagOverrides()).thenReturn(tagOverrides); List<AttributeMutation> attributeOverrides = new ArrayList<>(); attributeOverrides.add(AttributeMutation.newRemoveAttributeMutation("foo", 100)); attributeOverrides.add(AttributeMutation.newRemoveAttributeMutation("bar", 100)); when(mockAudienceManager.getAttributeOverrides()).thenReturn(attributeOverrides); when(mockDeferredScheduleClient.performRequest(new URL("https: .thenReturn(new Response.Builder<DeferredScheduleClient.Result>(200) .setResult(new DeferredScheduleClient.Result(true, message)) .build()); AutomationDriver.PrepareScheduleCallback callback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, triggerContext, callback); verify(mockIamManager).onPrepare(schedule.getId(), message, callback); }
@Test public void testPrepareDeferredScheduleMissedAudience() throws MalformedURLException, AuthException, RequestException { when(mockChannel.getId()).thenReturn("some channel"); CustomEvent event = CustomEvent.newBuilder("some event").build(); TriggerContext triggerContext = new TriggerContext(Triggers.newCustomEventTriggerBuilder().build(), event.toJsonValue()); Deferred deferredScheduleData = new Deferred(new URL("https: Schedule<? extends ScheduleData> schedule = Schedule.newBuilder(deferredScheduleData) .addTrigger(Triggers.newCustomEventTriggerBuilder().build()) .setAudience(Audience.newBuilder() .setMissBehavior(Audience.MISS_BEHAVIOR_SKIP) .build()) .build(); when(mockDeferredScheduleClient.performRequest(new URL("https: .thenReturn(new Response.Builder<DeferredScheduleClient.Result>(200) .setResult(new DeferredScheduleClient.Result(false, null)) .build()); AutomationDriver.PrepareScheduleCallback callback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, triggerContext, callback); verify(callback).onFinish(AutomationDriver.PREPARE_RESULT_SKIP); }
@Test public void testPrepareDeferredScheduleNoMessage() throws MalformedURLException, AuthException, RequestException { when(mockChannel.getId()).thenReturn("some channel"); Deferred deferredScheduleData = new Deferred(new URL("https: Schedule<? extends ScheduleData> schedule = Schedule.newBuilder(deferredScheduleData) .addTrigger(Triggers.newCustomEventTriggerBuilder().build()) .setAudience(Audience.newBuilder() .setMissBehavior(Audience.MISS_BEHAVIOR_SKIP) .build()) .build(); when(mockDeferredScheduleClient.performRequest(new URL("https: .thenReturn(new Response.Builder<DeferredScheduleClient.Result>(200) .setResult(new DeferredScheduleClient.Result(true, null)) .build()); AutomationDriver.PrepareScheduleCallback callback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, callback); verify(callback).onFinish(AutomationDriver.PREPARE_RESULT_PENALIZE); }
@Test public void testPrepareDeferredScheduleFailedResponse() throws MalformedURLException, AuthException, RequestException { when(mockChannel.getId()).thenReturn("some channel"); Deferred deferredScheduleData = new Deferred(new URL("https: Schedule<? extends ScheduleData> schedule = Schedule.newBuilder(deferredScheduleData) .addTrigger(Triggers.newCustomEventTriggerBuilder().build()) .build(); when(mockDeferredScheduleClient.performRequest(new URL("https: .thenReturn(new Response.Builder<DeferredScheduleClient.Result>(400).build()); AutomationDriver.PrepareScheduleCallback callback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, callback); verifyZeroInteractions(callback); when(mockDeferredScheduleClient.performRequest(new URL("https: .thenReturn(new Response.Builder<DeferredScheduleClient.Result>(200) .setResult(new DeferredScheduleClient.Result(true, null)) .build()); runLooperTasks(); verify(callback).onFinish(AutomationDriver.PREPARE_RESULT_PENALIZE); }
@Test public void testPrepareDeferredScheduleNoResponse() throws MalformedURLException, AuthException, RequestException { when(mockChannel.getId()).thenReturn("some channel"); Deferred deferredScheduleData = new Deferred(new URL("https: Schedule<? extends ScheduleData> schedule = Schedule.newBuilder(deferredScheduleData) .addTrigger(Triggers.newCustomEventTriggerBuilder().build()) .build(); when(mockDeferredScheduleClient.performRequest(new URL("https: .thenThrow(new RequestException("neat")) .thenReturn(new Response.Builder<DeferredScheduleClient.Result>(200) .setResult(new DeferredScheduleClient.Result(true, null)) .build()); AutomationDriver.PrepareScheduleCallback callback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, callback); verifyZeroInteractions(callback); runLooperTasks(); verify(callback).onFinish(AutomationDriver.PREPARE_RESULT_PENALIZE); }
@Test public void testPrepareDeferredScheduleNoResponseNoRetry() throws MalformedURLException, AuthException, RequestException { when(mockChannel.getId()).thenReturn("some channel"); Deferred deferredScheduleData = new Deferred(new URL("https: Schedule<? extends ScheduleData> schedule = Schedule.newBuilder(deferredScheduleData) .addTrigger(Triggers.newCustomEventTriggerBuilder().build()) .build(); when(mockDeferredScheduleClient.performRequest(new URL("https: .thenThrow(new RequestException("neat")); AutomationDriver.PrepareScheduleCallback callback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, callback); verify(callback).onFinish(AutomationDriver.PREPARE_RESULT_PENALIZE); }
@Test public void testPrepareDeferredScheduleAuthException() throws MalformedURLException, AuthException, RequestException { when(mockChannel.getId()).thenReturn("some channel"); Deferred deferredScheduleData = new Deferred(new URL("https: Schedule<Deferred> schedule = Schedule.newBuilder(deferredScheduleData) .addTrigger(Triggers.newCustomEventTriggerBuilder().build()) .build(); when(mockDeferredScheduleClient.performRequest(new URL("https: .thenThrow(new AuthException("neat")) .thenReturn(new Response.Builder<DeferredScheduleClient.Result>(200) .setResult(new DeferredScheduleClient.Result(true, null)) .build()); AutomationDriver.PrepareScheduleCallback callback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, callback); verifyZeroInteractions(callback); runLooperTasks(); verify(callback).onFinish(AutomationDriver.PREPARE_RESULT_PENALIZE); }
@Test public void testPrepareDeferredScheduleNoChannel() throws MalformedURLException, AuthException, RequestException { when(mockChannel.getId()).thenReturn(null); Deferred deferredScheduleData = new Deferred(new URL("https: Schedule<? extends ScheduleData> schedule = Schedule.newBuilder(deferredScheduleData) .addTrigger(Triggers.newCustomEventTriggerBuilder().build()) .build(); AutomationDriver.PrepareScheduleCallback callback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, callback); verifyZeroInteractions(callback); }
@Test public void testInvalidScheduleOnPrepare() { InAppMessage message = InAppMessage.newBuilder() .setSource(InAppMessage.SOURCE_REMOTE_DATA) .setDisplayContent(new CustomDisplayContent(JsonValue.NULL)) .addAction("action_name", JsonValue.wrap("action_value")) .build(); Schedule<InAppMessage> schedule = Schedule.newBuilder(message) .addTrigger(Triggers.newAppInitTriggerBuilder().setGoal(1).build()) .build(); when(mockObserver.isRemoteSchedule(schedule)).thenReturn(true); when(mockObserver.isScheduleValid(schedule)).thenReturn(false); when(mockObserver.isUpToDate()).thenReturn(true); AutomationDriver.PrepareScheduleCallback callback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, callback); runLooperTasks(); verify(callback).onFinish(AutomationDriver.PREPARE_RESULT_INVALIDATE); }
@Test public void testAudienceConditionsCheckDefaultMissBehavior() { InAppMessage message = InAppMessage.newBuilder() .setDisplayContent(new CustomDisplayContent(JsonValue.NULL)) .build(); Schedule<InAppMessage> schedule = Schedule.newBuilder(message) .addTrigger(Triggers.newAppInitTriggerBuilder().setGoal(1).build()) .setAudience(Audience.newBuilder() .setNotificationsOptIn(true) .build()) .build(); AutomationDriver.PrepareScheduleCallback mockPrepareCallback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, mockPrepareCallback); verify(mockPrepareCallback).onFinish(AutomationDriver.PREPARE_RESULT_PENALIZE); }
@Test public void testAudienceConditionsCheckMissBehaviorCancel() { InAppMessage message = InAppMessage.newBuilder() .setDisplayContent(new CustomDisplayContent(JsonValue.NULL)) .build(); Schedule<InAppMessage> schedule = Schedule.newBuilder(message) .addTrigger(Triggers.newAppInitTriggerBuilder().setGoal(1).build()) .setAudience(Audience.newBuilder() .setNotificationsOptIn(true) .setMissBehavior(Audience.MISS_BEHAVIOR_CANCEL) .build()) .build(); AutomationDriver.PrepareScheduleCallback mockPrepareCallback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, mockPrepareCallback); verify(mockPrepareCallback).onFinish(AutomationDriver.PREPARE_RESULT_CANCEL); }
@Test public void testAudienceConditionsCheckMissBehaviorSkip() { Schedule<Actions> schedule = Schedule.newBuilder(new Actions(JsonMap.EMPTY_MAP)) .addTrigger(Triggers.newAppInitTriggerBuilder().setGoal(1).build()) .setAudience(Audience.newBuilder() .setNotificationsOptIn(true) .setMissBehavior(Audience.MISS_BEHAVIOR_SKIP) .build()) .build(); AutomationDriver.PrepareScheduleCallback mockPrepareCallback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, mockPrepareCallback); verify(mockPrepareCallback).onFinish(AutomationDriver.PREPARE_RESULT_SKIP); }
@Test public void testAudienceConditionsCheckMissBehaviorPenalize() { Schedule<Actions> schedule = Schedule.newBuilder(new Actions(JsonMap.EMPTY_MAP)) .addTrigger(Triggers.newAppInitTriggerBuilder().setGoal(1).build()) .setAudience(Audience.newBuilder() .setNotificationsOptIn(true) .setMissBehavior(Audience.MISS_BEHAVIOR_PENALIZE) .build()) .build(); AutomationDriver.PrepareScheduleCallback mockPrepareCallback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, mockPrepareCallback); verify(mockPrepareCallback).onFinish(AutomationDriver.PREPARE_RESULT_PENALIZE); }
@Test public void testAudienceConditionCheckWithTagGroups() { Map<String, Set<String>> tagGroups = new HashMap<>(); tagGroups.put("expected group", Collections.singleton("expected tag")); when(mockAudienceManager.getTags(tagGroups)).thenReturn(new TagGroupResult(true, tagGroups)); Schedule<Actions> schedule = Schedule.newBuilder(new Actions(JsonMap.EMPTY_MAP)) .addTrigger(Triggers.newAppInitTriggerBuilder().setGoal(1).build()) .setAudience(Audience.newBuilder() .setNotificationsOptIn(true) .setMissBehavior(Audience.MISS_BEHAVIOR_SKIP) .setTagSelector(TagSelector.tag("expected tag", "expected group")) .build()) .build(); AutomationDriver.PrepareScheduleCallback mockPrepareCallback = mock(AutomationDriver.PrepareScheduleCallback.class); driver.onPrepareSchedule(schedule, null, mockPrepareCallback); verify(mockPrepareCallback).onFinish(AutomationDriver.PREPARE_RESULT_SKIP); }