package com.appboy.ui.inappmessage; import android.content.Context; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import com.appboy.Constants; import com.appboy.enums.AppboyViewBounds; import com.appboy.models.IInAppMessage; import com.appboy.models.InAppMessageHtmlBase; import com.appboy.models.InAppMessageHtmlFull; import com.appboy.models.InAppMessageModal; import com.appboy.models.InAppMessageSlideup; import com.appboy.support.AppboyImageUtils; import com.appboy.support.AppboyLogger; import com.appboy.support.StringUtils; import com.appboy.support.WebContentUtils; import com.appboy.ui.support.FrescoLibraryUtils; import com.facebook.datasource.DataSource; import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.imagepipeline.core.ImagePipeline; import com.facebook.imagepipeline.request.ImageRequest; import java.io.File; public class AppboyAsyncInAppMessageDisplayer extends AsyncTask<IInAppMessage, Integer, IInAppMessage> { private static final String TAG = String.format("%s.%s", Constants.APPBOY_LOG_TAG_PREFIX, AppboyAsyncInAppMessageDisplayer.class.getName()); @Override protected IInAppMessage doInBackground(IInAppMessage... inAppMessages) { try { AppboyLogger.d(TAG, "Starting asynchronous in-app message preparation."); boolean assetDownloadSucceeded; IInAppMessage inAppMessage = inAppMessages[0]; Context applicationContext = AppboyInAppMessageManager.getInstance().getApplicationContext(); if (inAppMessage instanceof InAppMessageHtmlFull) { // Note, this will clear the IAM cache, which is OK because no other IAM is currently displaying // and AsyncTasks are are executed on a single thread, which guarantees no other IAM is // relying on the cache dir right now. // See http://developer.android.com/reference/android/os/AsyncTask.html#execute(Params...) assetDownloadSucceeded = prepareInAppMessageWithHtml(inAppMessage); } else { if (FrescoLibraryUtils.canUseFresco(applicationContext)) { assetDownloadSucceeded = prepareInAppMessageWithFresco(inAppMessage); } else { assetDownloadSucceeded = prepareInAppMessageWithBitmapDownload(inAppMessage); } } if (!assetDownloadSucceeded) { return null; } return inAppMessage; } catch (Exception e) { AppboyLogger.e(TAG, "Error running AsyncInAppMessageDisplayer", e); return null; } } @Override protected void onPostExecute(final IInAppMessage inAppMessage) { try { if (inAppMessage != null) { AppboyLogger.d(TAG, "Finished asynchronous in-app message preparation. Attempting to display in-app message."); Context applicationContext = AppboyInAppMessageManager.getInstance().getApplicationContext(); Handler mainLooperHandler = new Handler(applicationContext.getMainLooper()); mainLooperHandler.post(new Runnable() { @Override public void run() { AppboyLogger.d(TAG, "Displaying in-app message."); AppboyInAppMessageManager.getInstance().displayInAppMessage(inAppMessage, false); } }); } else { AppboyLogger.e(TAG, "Cannot display the in-app message because the in-app message was null."); } } catch (Exception e) { AppboyLogger.e(TAG, "Error running onPostExecute", e); } } /** * Prepares the In-App Message for displaying Html content. * * @param inAppMessage the In-App Message to be prepared * * @return whether or not asset download succeeded */ boolean prepareInAppMessageWithHtml(IInAppMessage inAppMessage) { InAppMessageHtmlBase inAppMessageHtml = (InAppMessageHtmlBase) inAppMessage; // If the local assets exist already, return right away. String localAssets = inAppMessageHtml.getLocalAssetsDirectoryUrl(); if (!StringUtils.isNullOrBlank(localAssets) && new File(localAssets).exists()) { AppboyLogger.i(TAG, "Local assets for html in-app message are already populated. Not downloading assets."); return true; } // Otherwise, return if no remote asset zip location is specified. if (StringUtils.isNullOrBlank(inAppMessageHtml.getAssetsZipRemoteUrl())) { AppboyLogger.i(TAG, "Html in-app message has no remote asset zip. Continuing with in-app message preparation."); return true; } // Otherwise, download the asset zip. Context applicationContext = AppboyInAppMessageManager.getInstance().getApplicationContext(); File internalStorageCacheDirectory = WebContentUtils.getHtmlInAppMessageAssetCacheDirectory(applicationContext); String localWebContentUrl = WebContentUtils.getLocalHtmlUrlFromRemoteUrl(internalStorageCacheDirectory, inAppMessageHtml.getAssetsZipRemoteUrl()); if (!StringUtils.isNullOrBlank(localWebContentUrl)) { AppboyLogger.d(TAG, "Local url for html in-app message assets is " + localWebContentUrl); inAppMessageHtml.setLocalAssetsDirectoryUrl(localWebContentUrl); return true; } else { AppboyLogger.w(TAG, String.format("Download of html content to local directory failed for remote url: %s . Returned local url is: %s", inAppMessageHtml.getAssetsZipRemoteUrl(), localWebContentUrl)); return false; } } /** * Prepares the In-App Message for displaying images using the Fresco library. The in-app * message must have a valid image url. * * @param inAppMessage the In-App Message to be prepared * @return whether or not asset download succeeded */ boolean prepareInAppMessageWithFresco(IInAppMessage inAppMessage) { // If the image already has a local Uri, it will be loaded into the SimpleDrawee view when the in-app // message view is instantiated. String localImageUrl = inAppMessage.getLocalImageUrl(); if (!StringUtils.isNullOrBlank(localImageUrl) && new File(localImageUrl).exists()) { AppboyLogger.i(TAG, "In-app message has local image url for Fresco display. Not downloading image."); inAppMessage.setImageDownloadSuccessful(true); return true; } else { // If we don't use the local image url, clear it out to ensure we use the correct image url // in the view factory. inAppMessage.setLocalImageUrl(null); } // Otherwise, return if no remote uri is specified. String remoteImageUrl = inAppMessage.getRemoteImageUrl(); if (StringUtils.isNullOrBlank(remoteImageUrl)) { AppboyLogger.w(TAG, "In-app message has no remote image url. Not downloading image."); return true; } // Otherwise, prefetch the image content via http://frescolib.org/docs/using-image-pipeline.html#prefetching ImagePipeline imagePipeline = Fresco.getImagePipeline(); // Create a request for the image ImageRequest imageRequest = ImageRequest.fromUri(remoteImageUrl); DataSource dataSource = imagePipeline.prefetchToDiskCache(imageRequest, new Object()); // Since we're in an asyncTask, we can wait for the also asynchronous prefetch by Fresco // to finish. while (!dataSource.isFinished()) { // Wait for the prefetch to finish } boolean downloadSucceeded = !dataSource.hasFailed(); if (downloadSucceeded) { inAppMessage.setImageDownloadSuccessful(true); } else { if (dataSource.getFailureCause() == null) { AppboyLogger.w(TAG, "Fresco disk prefetch failed with null cause for remote image url:" + remoteImageUrl); } else { AppboyLogger.w(TAG, "Fresco disk prefetch failed with cause: " + dataSource.getFailureCause().getMessage() + " with remote image url: " + remoteImageUrl); } } // Release the resource reference dataSource.close(); return downloadSucceeded; } /** * Prepares the In-App Message for displaying images using a bitmap downloader. The in-app * message must have a valid image url. * * @param inAppMessage the In-App Message to be prepared * @return whether or not the asset download succeeded */ boolean prepareInAppMessageWithBitmapDownload(IInAppMessage inAppMessage) { if (inAppMessage.getBitmap() != null) { AppboyLogger.i(TAG, "In-app message already contains image bitmap. Not downloading image from URL."); inAppMessage.setImageDownloadSuccessful(true); return true; } // If the image already has a local Uri, attempt to load it String localImageUrl = inAppMessage.getLocalImageUrl(); if (!StringUtils.isNullOrBlank(localImageUrl) && new File(localImageUrl).exists()) { AppboyLogger.i(TAG, "In-app message has local image url."); inAppMessage.setBitmap(AppboyImageUtils.getBitmap(Uri.parse(localImageUrl))); } // If loading fails or no local image is specified, download from the remote url. // Return if no remote uri is specified. if (inAppMessage.getBitmap() == null) { String remoteImageUrl = inAppMessage.getRemoteImageUrl(); if (!StringUtils.isNullOrBlank(remoteImageUrl)) { AppboyLogger.i(TAG, "In-app message has remote image url. Downloading."); // Try to sample the image for slideup and modal in-app messages Context applicationContext = AppboyInAppMessageManager.getInstance().getApplicationContext(); // By default, the image won't be sampled AppboyViewBounds viewBounds = AppboyViewBounds.NO_BOUNDS; if (inAppMessage instanceof InAppMessageSlideup) { viewBounds = AppboyViewBounds.IN_APP_MESSAGE_SLIDEUP; } else if (inAppMessage instanceof InAppMessageModal) { viewBounds = AppboyViewBounds.IN_APP_MESSAGE_MODAL; } inAppMessage.setBitmap(AppboyImageUtils.getBitmap(applicationContext, Uri.parse(remoteImageUrl), viewBounds)); } else { AppboyLogger.w(TAG, "In-app message has no remote image url. Not downloading image."); return true; } } if (inAppMessage.getBitmap() != null) { inAppMessage.setImageDownloadSuccessful(true); return true; } return false; } }