package com.appboy.ui.inappmessage; import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.webkit.WebView; import android.webkit.WebViewClient; import com.appboy.Constants; import com.appboy.models.IInAppMessage; import com.appboy.support.AppboyFileUtils; import com.appboy.support.AppboyLogger; import com.appboy.support.StringUtils; import com.appboy.ui.inappmessage.listeners.IInAppMessageWebViewClientListener; import com.appboy.ui.support.UriUtils; import java.util.Map; public class InAppMessageWebViewClient extends WebViewClient { private static final String TAG = String.format("%s.%s", Constants.APPBOY_LOG_TAG_PREFIX, InAppMessageWebViewClient.class.getName()); private static final String APPBOY_INAPP_MESSAGE_SCHEME = "appboy"; private static final String AUTHORITY_NAME_CLOSE = "close"; private static final String AUTHORITY_NAME_NEWSFEED = "feed"; private static final String AUTHORITY_NAME_CUSTOM_EVENT = "customEvent"; /** * The query key for the button id for tracking */ public static final String QUERY_NAME_BUTTON_ID = "abButtonId"; /** * The query key for opening links externally (i.e. outside your app). Url intents will be opened with * the INTENT.ACTION_VIEW intent. Links beginning with the appboy:// scheme are unaffected by this query key. */ public static final String QUERY_NAME_EXTERNAL_OPEN = "abExternalOpen"; /** * Query key for directing Appboy to open Url intents using the INTENT.ACTION_VIEW. */ public static final String QUERY_NAME_DEEPLINK = "abDeepLink"; public static final String JAVASCRIPT_PREFIX = "javascript:"; private IInAppMessageWebViewClientListener mInAppMessageWebViewClientListener; private final IInAppMessage mInAppMessage; private Context mContext; /** * @param inAppMessage the In-App Message being displayed in this WebView * @param inAppMessageWebViewClientListener the client listener. Should be non-null. */ public InAppMessageWebViewClient(Context context, IInAppMessage inAppMessage, IInAppMessageWebViewClientListener inAppMessageWebViewClientListener) { mInAppMessageWebViewClientListener = inAppMessageWebViewClientListener; mInAppMessage = inAppMessage; mContext = context; } @Override public void onPageFinished(WebView view, String url) { appendBridgeJavascript(view); } private void appendBridgeJavascript(WebView view) { String javascriptString = AppboyFileUtils.getAssetFileStringContents(mContext.getAssets(), "appboy-html-in-app-message-javascript-component.js"); if (javascriptString == null) { // Fail instead of present a broken WebView AppboyInAppMessageManager.getInstance().hideCurrentlyDisplayingInAppMessage(false); AppboyLogger.e(TAG, "Failed to get HTML in-app message javascript additions"); return; } view.loadUrl(JAVASCRIPT_PREFIX + javascriptString); } /** * Handles Appboy schemed ("appboy://") urls in the HTML content WebViews. If the url isn't Appboy schemed, then the url is passed * to the attached IInAppMessageWebViewClientListener. * <p/> * We expect the URLs to be hierarchical and have "appboy" equal the scheme. * For example, appboy://close is one such URL. * * @return true since all actions in Html In-App Messages are handled outside of the In-App Message itself. */ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (mInAppMessageWebViewClientListener == null) { AppboyLogger.i(TAG, "InAppMessageWebViewClient was given null IInAppMessageWebViewClientListener listener. Returning true."); return true; } if (StringUtils.isNullOrBlank(url)) { // Null or blank urls shouldn't be passed back to the WebView. We return true here to indicate // to the WebView that we handled the url. AppboyLogger.i(TAG, "InAppMessageWebViewClient.shouldOverrideUrlLoading was given null or blank url. Returning true."); return true; } Uri uri = Uri.parse(url); Bundle queryBundle = getBundleFromUrl(url); if (uri.getScheme().equals(APPBOY_INAPP_MESSAGE_SCHEME)) { // Check the authority String authority = uri.getAuthority(); if (authority.equals(AUTHORITY_NAME_CLOSE)) { mInAppMessageWebViewClientListener.onCloseAction(mInAppMessage, url, queryBundle); } else if (authority.equals(AUTHORITY_NAME_NEWSFEED)) { mInAppMessageWebViewClientListener.onNewsfeedAction(mInAppMessage, url, queryBundle); } else if (authority.equals(AUTHORITY_NAME_CUSTOM_EVENT)) { mInAppMessageWebViewClientListener.onCustomEventAction(mInAppMessage, url, queryBundle); } return true; } mInAppMessageWebViewClientListener.onOtherUrlAction(mInAppMessage, url, queryBundle); return true; } /** * Returns the string mapping of the query keys and values from the query string of the url. If the query string * contains duplicate keys, then the last key in the string will be kept. * * @param url the url * @return a bundle containing the key/value mapping of the query string. Will not be null. */ // Default visibility for testing static Bundle getBundleFromUrl(String url) { Bundle queryBundle = new Bundle(); if (StringUtils.isNullOrBlank(url)) { return queryBundle; } Uri uri = Uri.parse(url); Map<String, String> queryParameterMap = UriUtils.getQueryParameters(uri); for (String queryKeyName : queryParameterMap.keySet()) { String queryValue = queryParameterMap.get(queryKeyName); queryBundle.putString(queryKeyName, queryValue); } return queryBundle; } }