package com.appboy.ui.inappmessage; import android.app.Activity; import android.app.Application; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.os.Handler; import android.view.View; import android.view.animation.Animation; import com.appboy.Appboy; import com.appboy.Constants; import com.appboy.enums.inappmessage.Orientation; import com.appboy.events.IEventSubscriber; import com.appboy.events.InAppMessageEvent; import com.appboy.models.IInAppMessage; import com.appboy.models.InAppMessageFull; import com.appboy.models.InAppMessageHtmlFull; import com.appboy.models.InAppMessageModal; import com.appboy.models.InAppMessageSlideup; import com.appboy.support.AppboyLogger; import com.appboy.ui.inappmessage.factories.AppboyFullViewFactory; import com.appboy.ui.inappmessage.factories.AppboyHtmlFullViewFactory; import com.appboy.ui.inappmessage.factories.AppboyInAppMessageAnimationFactory; import com.appboy.ui.inappmessage.factories.AppboyModalViewFactory; import com.appboy.ui.inappmessage.factories.AppboySlideupViewFactory; import com.appboy.ui.inappmessage.listeners.AppboyDefaultHtmlInAppMessageActionListener; import com.appboy.ui.inappmessage.listeners.AppboyDefaultInAppMessageManagerListener; import com.appboy.ui.inappmessage.listeners.AppboyInAppMessageViewLifecycleListener; import com.appboy.ui.inappmessage.listeners.AppboyInAppMessageWebViewClientListener; import com.appboy.ui.inappmessage.listeners.IHtmlInAppMessageActionListener; import com.appboy.ui.inappmessage.listeners.IInAppMessageManagerListener; import com.appboy.ui.inappmessage.listeners.IInAppMessageViewLifecycleListener; import com.appboy.ui.inappmessage.listeners.IInAppMessageWebViewClientListener; import com.appboy.ui.support.ViewUtils; import java.util.Stack; import java.util.concurrent.atomic.AtomicBoolean; /** * The AppboyInAppMessageManager is used to display in-app messages that are either sent down from Appboy * or are created natively in the host app. It will only show one in-app message at a time and will * place all other in-app messages onto a stack. The AppboyInAppMessageManager will also keep track of in-app * impressions and clicks, which can be viewed on the dashboard. * <p/> * When an in-app message is received from Appboy, the * {@link IInAppMessageManagerListener#onInAppMessageReceived(com.appboy.models.IInAppMessage)} * method is called (if set). If this method returns true, that signals to the AppboyInAppMessageManager that * the in-app message will be handled by the host app and that it should not be displayed by the * AppboyInAppMessageManager. This method should be used if you choose to display the in-app message in a custom * way. If false is returned, the AppboyInAppMessageManager attempts to display the in-app message. * <p/> * If there is already an in-app message being displayed, the new in-app message will be put onto the top of the * stack and can be displayed at a later time. If there is no in-app message being displayed, then the * {@link IInAppMessageManagerListener#beforeInAppMessageDisplayed(com.appboy.models.IInAppMessage)} * will be called. The {@link InAppMessageOperation} return value can be used to * control when the in-app message should be displayed. A suggested usage of this method would be to delay * in-app message messages in certain parts of the app by returning {@link InAppMessageOperation#DISPLAY_LATER} * when in-app message would be distracting to the users app experience. If the method returns * {@link InAppMessageOperation#DISPLAY_NOW} then the in-app message will be displayed * immediately. * <p/> * The {@link IInAppMessageManagerListener#onInAppMessageClicked(com.appboy.models.IInAppMessage, InAppMessageCloser)} * and {@link IInAppMessageManagerListener#onInAppMessageDismissed(com.appboy.models.IInAppMessage)} * methods can be used to override the default click and dismiss behavior. * <p/> * By default, in-app messages fade in and out from view. The slideup type of in-app message slides in and out of view * can be dismissed by swiping the view horizontally. If the in-app message's DismissType is set to AUTO_DISMISS, * then the in-app message will animate out of view once the set duration time has elapsed. * <p/> * The default view used to display slideup, modal, and full in-app messages * is defined by res/layout/com_appboy_inappmessage_*.xml, where * is the message type. In * order to use a custom view, you must create a custom view factory using the * {@link AppboyInAppMessageManager#setCustomInAppMessageViewFactory(IInAppMessageViewFactory inAppMessageViewFactory)} method. * <p/> * A new in-app message {@link android.view.View} object is created when a in-app message is displayed and also * when the user navigates away to another {@link android.app.Activity}. This happens so that the * Activity can be garbage collected and does not create a memory leak. For that reason, the * {@link AppboyInAppMessageManager#registerInAppMessageManager(android.app.Activity)} * and {@link AppboyInAppMessageManager#unregisterInAppMessageManager(android.app.Activity)} * must be called in the {@link android.app.Activity#onResume()} and {@link android.app.Activity#onPause()} * methods of every Activity. */ public final class AppboyInAppMessageManager { private static final String TAG = String.format("%s.%s", Constants.APPBOY_LOG_TAG_PREFIX, AppboyInAppMessageManager.class.getName()); private static volatile AppboyInAppMessageManager sInstance = null; private final Stack<IInAppMessage> mInAppMessageStack = new Stack<IInAppMessage>(); private Activity mActivity; private IEventSubscriber<InAppMessageEvent> mInAppMessageEventSubscriber; private IInAppMessageViewFactory mCustomInAppMessageViewFactory; private IInAppMessageAnimationFactory mCustomInAppMessageAnimationFactory; private IInAppMessageViewWrapper mInAppMessageViewWrapper; private IInAppMessage mCarryoverInAppMessage; private IInAppMessage mUnRegisteredInAppMessage; private AtomicBoolean mDisplayingInAppMessage = new AtomicBoolean(false); private Context mApplicationContext; private Integer mOriginalOrientation; // view listeners private final IInAppMessageWebViewClientListener mInAppMessageWebViewClientListener = new AppboyInAppMessageWebViewClientListener(); private final IInAppMessageViewLifecycleListener mInAppMessageViewLifecycleListener = new AppboyInAppMessageViewLifecycleListener(); // manager listeners private IInAppMessageManagerListener mDefaultInAppMessageManagerListener = new AppboyDefaultInAppMessageManagerListener(); private IInAppMessageManagerListener mCustomInAppMessageManagerListener; // html action listeners private IHtmlInAppMessageActionListener mDefaultHtmlInAppMessageActionListener = new AppboyDefaultHtmlInAppMessageActionListener(); private IHtmlInAppMessageActionListener mCustomHtmlInAppMessageActionListener; // view factories private IInAppMessageViewFactory mInAppMessageSlideupViewFactory = new AppboySlideupViewFactory(); private IInAppMessageViewFactory mInAppMessageModalViewFactory = new AppboyModalViewFactory(); private IInAppMessageViewFactory mInAppMessageFullViewFactory = new AppboyFullViewFactory(); private IInAppMessageViewFactory mInAppMessageHtmlFullViewFactory = new AppboyHtmlFullViewFactory(mInAppMessageWebViewClientListener); // animation factory private IInAppMessageAnimationFactory mInAppMessageAnimationFactory = new AppboyInAppMessageAnimationFactory(); public static AppboyInAppMessageManager getInstance() { if (sInstance == null) { synchronized (AppboyInAppMessageManager.class) { if (sInstance == null) { sInstance = new AppboyInAppMessageManager(); } } } return sInstance; } /** * Ensures the InAppMessageManager is subscribed in-app message events if not already subscribed. * Before this method gets called, the InAppMessageManager is not subscribed to in-app message events * and cannot display them. Every call to registerInAppMessageManager() calls this method. * <p/> * If events with triggers are logged before the first call to registerInAppMessageManager(), then the * corresponding in-app message won't display. Thus, if logging events with triggers before the first call * to registerInAppMessageManager(), then call this method to ensure that in-app message events * are correctly handled by the AppboyInAppMessageManager. * <p/> * For example, if logging custom events with triggers in your first activity's onCreate(), be sure * to call this method manually beforehand so that the in-app message will get displayed by the time * registerInAppMessageManager() gets called. * * @param context The application context */ public void ensureSubscribedToInAppMessageEvents(Context context) { if (mInAppMessageEventSubscriber == null) { AppboyLogger.d(TAG, "Subscribing in-app message event subscriber"); mInAppMessageEventSubscriber = createInAppMessageEventSubscriber(); Appboy.getInstance(context).subscribeToNewInAppMessages(mInAppMessageEventSubscriber); } } /** * Registers the in-app message manager, which will listen to and display incoming in-app messages. The * current Activity is required in order to properly inflate and display the in-app message view. * <p/> * Important note: Every Activity must call registerInAppMessageManager in the onResume lifecycle * method, otherwise in-app messages may be lost! * <p/> * This method also calls {@link AppboyInAppMessageManager#ensureSubscribedToInAppMessageEvents(Context)}. * To be sure that no in-app messages are lost, you should call {@link AppboyInAppMessageManager#ensureSubscribedToInAppMessageEvents(Context)} as early * as possible in your app, preferably in your {@link Application#onCreate()}. * @param activity The current Activity. */ public void registerInAppMessageManager(Activity activity) { AppboyLogger.d(TAG, "registerInAppMessageManager called"); // We need the current Activity so that we can inflate or programmatically create the in-app message // View for each Activity. We cannot share the View because doing so would create a memory leak. mActivity = activity; if (mActivity != null && mApplicationContext == null) { // Note, because the IAMManager is a singleton and doesn't have any dependencies passed in, // we cache the application context here because it's not available (as it normally would be // from Appboy initialization). mApplicationContext = mActivity.getApplicationContext(); } // We have a special check to see if the host app switched to a different Activity (or recreated // the same Activity during an orientation change) so that we can redisplay the in-app message. if (mCarryoverInAppMessage != null) { AppboyLogger.d(TAG, "Requesting display of carryover in-app message."); mCarryoverInAppMessage.setAnimateIn(false); displayInAppMessage(mCarryoverInAppMessage, true); mCarryoverInAppMessage = null; } else if (mUnRegisteredInAppMessage != null) { AppboyLogger.d(TAG, "Adding previously unregistered in-app message."); addInAppMessage(mUnRegisteredInAppMessage); mUnRegisteredInAppMessage = null; } ensureSubscribedToInAppMessageEvents(mApplicationContext); } /** * Unregisters the in-app message manager. * * @param activity The current Activity. */ public void unregisterInAppMessageManager(Activity activity) { AppboyLogger.d(TAG, "unregisterInAppMessageManager called"); // If there is an in-app message being displayed when the host app transitions to another Activity (or // requests an orientation change), we save it in memory so that we can redisplay it when the // operation is done. if (mInAppMessageViewWrapper != null) { ViewUtils.removeViewFromParent(mInAppMessageViewWrapper.getInAppMessageView()); // Only continue if we're not animating a close if (mInAppMessageViewWrapper.getIsAnimatingClose()) { mInAppMessageViewLifecycleListener.afterClosed(mInAppMessageViewWrapper.getInAppMessage()); mCarryoverInAppMessage = null; } else { mCarryoverInAppMessage = mInAppMessageViewWrapper.getInAppMessage(); } mInAppMessageViewWrapper = null; } else { mCarryoverInAppMessage = null; } mActivity = null; mDisplayingInAppMessage.set(false); } /** * Assigns a custom IInAppMessageManagerListener that will be used when displaying in-app messages. To revert * back to the default IInAppMessageManagerListener, call the setCustomInAppMessageManagerListener method with * null. * * @param inAppMessageManagerListener A custom IInAppMessageManagerListener or null (to revert back to the * default IInAppMessageManagerListener). */ public void setCustomInAppMessageManagerListener(IInAppMessageManagerListener inAppMessageManagerListener) { AppboyLogger.d(TAG, "Custom InAppMessageManagerListener set"); mCustomInAppMessageManagerListener = inAppMessageManagerListener; } /** * Assigns a custom IHtmlInAppMessageActionListener that will be used during the display of Html In-App Messages. * * @param htmlInAppMessageActionListener A custom IHtmlInAppMessageActionListener or null (to revert back to the * default IHtmlInAppMessageActionListener). */ public void setCustomHtmlInAppMessageActionListener(IHtmlInAppMessageActionListener htmlInAppMessageActionListener) { AppboyLogger.d(TAG, "Custom htmlInAppMessageActionListener set"); mCustomHtmlInAppMessageActionListener = htmlInAppMessageActionListener; } /** * Assigns a custom IInAppMessageAnimationFactory that will be used to animate the in-app message View. To revert * back to the default IInAppMessageAnimationFactory, call the setCustomInAppMessageAnimationFactory method with null. * * @param inAppMessageAnimationFactory A custom IInAppMessageAnimationFactory or null (to revert back to the default * IInAppMessageAnimationFactory). */ public void setCustomInAppMessageAnimationFactory(IInAppMessageAnimationFactory inAppMessageAnimationFactory) { AppboyLogger.d(TAG, "Custom InAppMessageAnimationFactory set"); mCustomInAppMessageAnimationFactory = inAppMessageAnimationFactory; } /** * Assigns a custom IInAppMessageViewFactory that will be used to create the in-app message View. To revert * back to the default IInAppMessageViewFactory, call the setCustomInAppMessageViewFactory method with null. * * @param inAppMessageViewFactory A custom IInAppMessageViewFactory or null (to revert back to the default * IInAppMessageViewFactory). */ public void setCustomInAppMessageViewFactory(IInAppMessageViewFactory inAppMessageViewFactory) { AppboyLogger.d(TAG, "Custom InAppMessageViewFactory set"); mCustomInAppMessageViewFactory = inAppMessageViewFactory; } /** * Provides a in-app message that will then be handled by the in-app message manager. If no in-app message is being * displayed, it will attempt to display the in-app message immediately. * * @param inAppMessage The in-app message to add. */ public void addInAppMessage(IInAppMessage inAppMessage) { mInAppMessageStack.push(inAppMessage); requestDisplayInAppMessage(); } /** * Asks the InAppMessageManager to display the next in-app message if one is not currently being displayed. * If one is being displayed, this method will return false and will not display the next in-app message. * * @return A boolean value indicating whether an asynchronous task to display the in-app message display was executed. */ public boolean requestDisplayInAppMessage() { try { if (mActivity == null) { if (!mInAppMessageStack.empty()) { AppboyLogger.w(TAG, "No activity is currently registered to receive in-app messages. Saving in-app message as unregistered " + "in-app message. It will automatically be displayed when the next activity registers to receive in-app messages."); mUnRegisteredInAppMessage = mInAppMessageStack.pop(); } else { AppboyLogger.d(TAG, "No activity is currently registered to receive in-app messages and the in-app message stack is empty. Doing nothing."); } return false; } if (mDisplayingInAppMessage.get()) { AppboyLogger.d(TAG, "A in-app message is currently being displayed. Ignoring request to display in-app message."); return false; } if (mInAppMessageStack.isEmpty()) { AppboyLogger.d(TAG, "The in-app message stack is empty. No in-app message will be displayed."); return false; } final IInAppMessage inAppMessage = mInAppMessageStack.pop(); InAppMessageOperation inAppMessageOperation = getInAppMessageManagerListener().beforeInAppMessageDisplayed(inAppMessage); switch (inAppMessageOperation) { case DISPLAY_NOW: AppboyLogger.d(TAG, "The IInAppMessageManagerListener method beforeInAppMessageDisplayed returned DISPLAY_NOW. The " + "in-app message will be displayed."); break; case DISPLAY_LATER: AppboyLogger.d(TAG, "The IInAppMessageManagerListener method beforeInAppMessageDisplayed returned DISPLAY_LATER. The " + "in-app message will be pushed back onto the stack."); mInAppMessageStack.push(inAppMessage); return false; case DISCARD: AppboyLogger.d(TAG, "The IInAppMessageManagerListener method beforeInAppMessageDisplayed returned DISCARD. The " + "in-app message will not be displayed and will not be put back on the stack."); return false; default: AppboyLogger.e(TAG, "The IInAppMessageManagerListener method beforeInAppMessageDisplayed returned null instead of a " + "InAppMessageOperation. Ignoring the in-app message. Please check the IInAppMessageStackBehaviour " + "implementation."); return false; } Handler mainLooperHandler = new Handler(mApplicationContext.getMainLooper()); mainLooperHandler.post(new Runnable() { @Override public void run() { new AppboyAsyncInAppMessageDisplayer().execute(inAppMessage); } }); return true; } catch (Exception e) { AppboyLogger.e(TAG, "Error running requestDisplayInAppMessage", e); return false; } } /** * Hides any currently displaying in-app message. Note that in-app message animation * is configurable on the in-app message model itself and should be configured there. * * @param dismissed whether the message was dismissed by the user. If dismissed is true, * IInAppMessageViewLifecycleListener.onDismissed() will be called on the current * IInAppMessageViewLifecycleListener. */ public void hideCurrentlyDisplayingInAppMessage(boolean dismissed) { IInAppMessageViewWrapper inAppMessageWrapperView = mInAppMessageViewWrapper; if (inAppMessageWrapperView != null) { if (dismissed) { mInAppMessageViewLifecycleListener.onDismissed(inAppMessageWrapperView.getInAppMessageView(), inAppMessageWrapperView.getInAppMessage()); } inAppMessageWrapperView.close(); } } public IInAppMessageManagerListener getInAppMessageManagerListener() { return mCustomInAppMessageManagerListener != null ? mCustomInAppMessageManagerListener : mDefaultInAppMessageManagerListener; } public IHtmlInAppMessageActionListener getHtmlInAppMessageActionListener() { return mCustomHtmlInAppMessageActionListener != null ? mCustomHtmlInAppMessageActionListener : mDefaultHtmlInAppMessageActionListener; } public Activity getActivity() { return mActivity; } public Context getApplicationContext() { return mApplicationContext; } public void resetAfterInAppMessageClose() { mInAppMessageViewWrapper = null; mDisplayingInAppMessage.set(false); if (mActivity != null && mOriginalOrientation != null) { mActivity.setRequestedOrientation(mOriginalOrientation); AppboyLogger.d(TAG, "Setting requested orientation to original orientation " + mOriginalOrientation); mOriginalOrientation = null; } } private IInAppMessageAnimationFactory getInAppMessageAnimationFactory() { return mCustomInAppMessageAnimationFactory != null ? mCustomInAppMessageAnimationFactory : mInAppMessageAnimationFactory; } private IInAppMessageViewFactory getInAppMessageViewFactory(IInAppMessage inAppMessage) { if (mCustomInAppMessageViewFactory != null) { return mCustomInAppMessageViewFactory; } else if (inAppMessage instanceof InAppMessageSlideup) { return mInAppMessageSlideupViewFactory; } else if (inAppMessage instanceof InAppMessageModal) { return mInAppMessageModalViewFactory; } else if (inAppMessage instanceof InAppMessageFull) { return mInAppMessageFullViewFactory; } else if (inAppMessage instanceof InAppMessageHtmlFull) { return mInAppMessageHtmlFullViewFactory; } else { return null; } } boolean displayInAppMessage(IInAppMessage inAppMessage, boolean isCarryOver) { // Note: for mDisplayingInAppMessage to be accurate it requires this method does not exit anywhere but the at the end // of this try/catch when we know whether we are successfully displaying the IAM or not. if (!mDisplayingInAppMessage.compareAndSet(false, true)) { AppboyLogger.d(TAG, "A in-app message is currently being displayed. Adding in-app message back on the stack."); mInAppMessageStack.push(inAppMessage); return false; } try { if (mActivity == null) { mCarryoverInAppMessage = inAppMessage; throw new Exception("No activity is currently registered to receive in-app messages. Registering in-app message as carry-over " + "in-app message. It will automatically be displayed when the next activity registers to receive in-app messages."); } if (!isCarryOver) { long inAppMessageExpirationTimestamp = inAppMessage.getExpirationTimestamp(); if (inAppMessageExpirationTimestamp > 0) { long currentTimeMillis = System.currentTimeMillis(); if (currentTimeMillis > inAppMessageExpirationTimestamp) { throw new Exception(String.format("In-app message is expired. Doing nothing. Expiration: $%d. Current time: %d", inAppMessageExpirationTimestamp, currentTimeMillis)); } } else { AppboyLogger.d(TAG, "Expiration timestamp not defined. Continuing."); } } else { AppboyLogger.d(TAG, "Not checking expiration status for carry-over in-app message."); } if (!verifyOrientationStatus(inAppMessage)) { throw new Exception("Current orientation did not match specified orientation for in-app message. Doing nothing."); } IInAppMessageViewFactory inAppMessageViewFactory = getInAppMessageViewFactory(inAppMessage); if (inAppMessageViewFactory == null) { throw new Exception("ViewFactory from getInAppMessageViewFactory was null."); } final View inAppMessageView = inAppMessageViewFactory.createInAppMessageView(mActivity, inAppMessage); if (inAppMessageView == null) { throw new Exception("The in-app message view returned from the IInAppMessageViewFactory was null. The in-app message will " + "not be displayed and will not be put back on the stack."); } if (inAppMessageView.getParent() != null) { throw new Exception("The in-app message view returned from the IInAppMessageViewFactory already has a parent. This " + "is a sign that the view is being reused. The IInAppMessageViewFactory method createInAppMessageView" + "must return a new view without a parent. The in-app message will not be displayed and will not " + "be put back on the stack."); } Animation openingAnimation = getInAppMessageAnimationFactory().getOpeningAnimation(inAppMessage); Animation closingAnimation = getInAppMessageAnimationFactory().getClosingAnimation(inAppMessage); if (inAppMessageView instanceof IInAppMessageImmersiveView) { AppboyLogger.d(TAG, "Creating view wrapper for immersive in-app message."); IInAppMessageImmersiveView inAppMessageViewImmersive = (IInAppMessageImmersiveView) inAppMessageView; mInAppMessageViewWrapper = new InAppMessageViewWrapper(inAppMessageView, inAppMessage, mInAppMessageViewLifecycleListener, openingAnimation, closingAnimation, inAppMessageViewImmersive.getMessageClickableView(), inAppMessageViewImmersive.getMessageButtonViews(), inAppMessageViewImmersive.getMessageCloseButtonView()); } else if (inAppMessageView instanceof IInAppMessageView) { AppboyLogger.d(TAG, "Creating view wrapper for base in-app message."); IInAppMessageView inAppMessageViewBase = (IInAppMessageView) inAppMessageView; mInAppMessageViewWrapper = new InAppMessageViewWrapper(inAppMessageView, inAppMessage, mInAppMessageViewLifecycleListener, openingAnimation, closingAnimation, inAppMessageViewBase.getMessageClickableView()); } else { AppboyLogger.d(TAG, "Creating view wrapper for in-app message."); mInAppMessageViewWrapper = new InAppMessageViewWrapper(inAppMessageView, inAppMessage, mInAppMessageViewLifecycleListener, openingAnimation, closingAnimation, inAppMessageView); } mInAppMessageViewWrapper.open(mActivity); return true; } catch (Exception e) { AppboyLogger.e(TAG, "Could not display in-app message", e); resetAfterInAppMessageClose(); return false; } } /** * * For in-app messages that have a preferred orientation, locks the screen orientation and * returns true if the screen is currently in the preferred orientation. If the screen is not * currently in the preferred orientation, returns false. * * Always returns true for tablets, regardless of current orientation. * * Always returns true if the in-app message doesn't have a preferred orientation. * * @param inAppMessage * @return */ boolean verifyOrientationStatus(IInAppMessage inAppMessage) { if (ViewUtils.isRunningOnTablet(mActivity)) { AppboyLogger.d(TAG, "Running on tablet. In-app message can be displayed in any orientation."); return true; } Orientation preferredOrientation = inAppMessage.getOrientation(); if (preferredOrientation == null) { AppboyLogger.d(TAG, "No orientation specified. In-app message can be displayed in any orientation."); return true; } if (preferredOrientation == Orientation.ANY) { AppboyLogger.d(TAG, "Any orientation specified. In-app message can be displayed in any orientation."); return true; } int currentScreenOrientation = mActivity.getResources().getConfiguration().orientation; if (currentOrientationIsValid(currentScreenOrientation, preferredOrientation)) { if (mOriginalOrientation == null) { AppboyLogger.d(TAG, "Requesting orientation lock."); mOriginalOrientation = mActivity.getRequestedOrientation(); mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); } return true; } return false; } private boolean currentOrientationIsValid(int currentScreenOrientation, Orientation preferredOrientation) { if (currentScreenOrientation == Configuration.ORIENTATION_LANDSCAPE && preferredOrientation == Orientation.LANDSCAPE) { AppboyLogger.d(TAG, "Current and preferred orientation are landscape."); return true; } else if (currentScreenOrientation == Configuration.ORIENTATION_PORTRAIT && preferredOrientation == Orientation.PORTRAIT) { AppboyLogger.d(TAG, "Current and preferred orientation are portrait."); return true; } else { AppboyLogger.d(TAG, String.format("Current orientation %d and preferred orientation %s don't match", currentScreenOrientation, preferredOrientation)); return false; } } private IEventSubscriber<InAppMessageEvent> createInAppMessageEventSubscriber() { return new IEventSubscriber<InAppMessageEvent>() { @Override public void trigger(InAppMessageEvent event) { if (getInAppMessageManagerListener().onInAppMessageReceived(event.getInAppMessage())) { return; } addInAppMessage(event.getInAppMessage()); } }; } }