package com.malcom.library.android.module.campaign;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.location.Location;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import com.malcom.library.android.MCMDefines;
import com.malcom.library.android.module.campaign.MCMCampaignDTO.CampaignPosition;
import com.malcom.library.android.module.core.MCMCoreAdapter;
import com.malcom.library.android.utils.LocationUtils;
import com.malcom.library.android.utils.MCMUtils;
import com.malcom.library.android.utils.ToolBox;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static com.malcom.library.android.module.campaign.MCMCampaignDTO.CampaignType;
/**
* Handles most of campaigns logic.
*/
public class MCMCampaignAdapter implements MCMCampaignBannerView.MCMCampaignBannerDelegate {
// TODO: This class is messy and problematic. We must refactor or rebuild it from scratch.
// For example, it's a singleton with (a lot of) state, so it's not thread-safe.
// In the meanwhile, to mitigate that, I've added the type argument in {@link #getInstance(CampaignType)} to return a
// different instance for each campaign type. That way, apps can use different campaign types at the same time with less risk.
// Also, it uses Handlers instead of AsyncTasks tasks. That makes the class very difficult to use from background threads.
public static int CAMPAIGN_DEFAULT_DURATION = -1;
private static Map<CampaignType, MCMCampaignAdapter> instances = new HashMap<CampaignType, MCMCampaignAdapter>();
private ViewGroup bannerLayout;
private String campaignResource = "";
private String malcomAppId;
private CampaignType type;
private Activity activity;
private int duration; //integer to set the duration of the banner
private RequestCampaignReceiver receiver; //callback to execute when the campaign request is done
private MCMCampaignNotifiedDelegate delegate;
private Integer loadingImageResId;
public interface RequestCampaignReceiver {
/**
* @param banners
*/
public void onReceivedPromotions(List<MCMCampaignBannerView> banners);
public void onRequestFailed(String errorMessage);
}
private Handler mHandler = new Handler();
// Exists only to defeat instantiation.
protected MCMCampaignAdapter() {
}
/**
* Gets the singleton instance of this class for the given campaign type.
*
* @return instance of MCMCampaignAdapter.
*/
public static synchronized MCMCampaignAdapter getInstance(CampaignType type) {
if (!instances.containsKey(type)) {
instances.put(type, new MCMCampaignAdapter());
}
return instances.get(type);
}
/**
* Adds a campaign banner to an activity.
*
* @param activity - your activity.
* @param campaignType - the type of campaign requested
* @param duration - the duration to show the campaign banner in seconds
* @param delegate - This is the delegate.
*/
public void addBanner(Activity activity, CampaignType campaignType, int duration, MCMCampaignNotifiedDelegate delegate, Integer loadingImgResId) {
this.activity = activity;
this.type = campaignType;
setDuration(duration);
this.delegate = delegate;
this.loadingImageResId = loadingImgResId;
mHandler.removeCallbacks(mRemoveCampaignBanner);
makeRequest();
}
public void requestBanner(Activity activity, CampaignType campaignType, RequestCampaignReceiver receiver) {
this.activity = activity;
this.type = campaignType;
this.receiver = receiver;
makeRequest();
}
public void addRateAlert(Activity activity, MCMCampaignNotifiedDelegate delegate) {
this.activity = activity;
this.type = CampaignType.IN_APP_RATE_MY_APP;
this.delegate = delegate;
makeRequest();
}
/**
* Method that removes the banner and notifies it to delegate if delegate is set.
*
* @since 1.0.1
*/
public void removeCurrentBanner(Activity activity) {
// Get layout elements
int resBannerLayoutID = activity.getResources().getIdentifier(MCMCampaignDefines.RES_ID_LAYOUT, "id", activity.getPackageName());
bannerLayout = (RelativeLayout) activity.findViewById(resBannerLayoutID);
if (bannerLayout!=null) {
bannerLayout.setVisibility(View.GONE);
}
notifyCampaignDidFinish();
}
/**
* Method that removes the banner and notifies it to delegate if delegate is set when time is over (duration)
*
* @since 1.0.1
*/
public void finishCampaign() {
this.bannerLayout.setVisibility(View.GONE);
notifyCampaignDidFinish();
}
private void makeRequest() {
// Create URL to get campaigns of my app
String malcomBaseUrl = MCMCoreAdapter.getInstance().coreGetProperty(MCMCoreAdapter.PROPERTIES_MALCOM_BASEURL);
malcomAppId = MCMUtils.getEncodedUDID(MCMCoreAdapter.getInstance().coreGetProperty(MCMCoreAdapter.PROPERTIES_MALCOM_APPID));
String devideId = MCMUtils.getEncodedUDID(ToolBox.device_getId(activity.getApplicationContext()));
campaignResource = MCMCampaignDefines.CAMPAIGN_URL.replace(MCMCampaignDefines.APP_ID_TAG, malcomAppId)
.replace(MCMCampaignDefines.UDID_TAG, devideId);
String urlCampaign = malcomBaseUrl + campaignResource;
//Add the location to the URL
Location lastKnownLocation = LocationUtils.getLocation(activity.getApplicationContext());
if (lastKnownLocation!=null) {
urlCampaign = urlCampaign+"?lat="+lastKnownLocation.getLatitude()
+"&lng="+lastKnownLocation.getLongitude();
}
// Launch request to get campaigns data
new MCMCampaignAsyncTasks.DownloadCampaignFile(type, this).execute(urlCampaign);
}
/**
* Method that filters the elements for current campaign type and calls the needed operation after the filtered
*
* @param campaignsArray the campaigns collected from the server's response
*/
protected void processResponse(ArrayList<MCMCampaignDTO> campaignsArray) {
//Gets the campaigns for the current type
MCMCampaignDTO selectedCampaign = null;
ArrayList<MCMCampaignDTO> filteredArray = MCMCampaignsLogics.getFilteredCampaigns(campaignsArray, type);
if(type == CampaignType.IN_APP_CROSS_SELLING) {
ArrayList<MCMCampaignDTO> externalCampaignsArray = MCMCampaignsLogics.getFilteredCampaigns(campaignsArray, CampaignType.IN_APP_EXTERNAL_URL);
filteredArray.addAll(externalCampaignsArray);
}
//If there is at least one campaign
if (filteredArray.size() > 0) {
selectedCampaign = MCMCampaignsLogics.getCampaignPerWeight(filteredArray);
}
if (selectedCampaign != null) {
if (type == CampaignType.IN_APP_CROSS_SELLING || type == CampaignType.IN_APP_EXTERNAL_URL || type == CampaignType.IN_APP_PROMOTION) {
if (receiver == null) {
createBanner(selectedCampaign);
} else {
receiver.onReceivedPromotions(createBannersList(activity, filteredArray));
}
} else if (type == CampaignType.IN_APP_RATE_MY_APP) {
//Show the dialog if it's necessary
if (MCMCampaignsLogics.shouldShowDialog(activity.getApplicationContext(),selectedCampaign)) {
createRateDialog(selectedCampaign);
}
//Update the session number
MCMCampaignsLogics.updateRateDialogSession(activity.getApplicationContext(),selectedCampaign);
}
} else {
notifyCampaignDidFail("There is no campaign to show");
}
}
/**
* Initiates banner layout depending on position.
* Launches request to get remote image.
*
* @param campaign - the campaign data to show.
*/
protected void createBanner(MCMCampaignDTO campaign) {
Log.d(MCMCampaignDefines.LOG_TAG, "createBanner - type: " + type + " campaign: " + campaign);
// Get layout elements
try {
int resBannerLayoutID = activity.getResources().getIdentifier(MCMCampaignDefines.RES_ID_LAYOUT, "id", activity.getPackageName());
if (resBannerLayoutID==0) {
throw new Exception("The layout with id = \"" + MCMCampaignDefines.RES_ID_LAYOUT + "\" was not found ");
}
bannerLayout = (ViewGroup) activity.findViewById(resBannerLayoutID);
//Configures the layout
configureCampaignLayout(campaign, bannerLayout);
//Creates the bannerView with the campaign data
MCMCampaignBannerView bannerView = new MCMCampaignBannerView(activity, campaign, loadingImageResId);
//Calculates the banner height
float density = activity.getResources().getDisplayMetrics().density;
int bannerHeight = (int) (MCMCampaignDefines.BANNER_SIZE_HEIGHT * density + 0.5f);
if (campaign.isFullScreen()) {
bannerHeight = ViewGroup.LayoutParams.MATCH_PARENT;
}
//Configures the banner view
bannerView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,bannerHeight));
bannerView.setDelegate(this);
//When a bannerView is added to a view, the remoteimage starts to download
bannerLayout.addView(bannerView);
// Config close button (if banner shows on full screen)
if (campaign.isFullScreen()) {
if (bannerLayout instanceof RelativeLayout)
addCloseButton((RelativeLayout) bannerLayout);
else
Log.w(MCMCampaignDefines.LOG_TAG, MCMCampaignDefines.RES_ID_LAYOUT + " is not a RelativeLayout so close button won't be added");
}
//if duration is not zero the banner will be removed automatically
if (duration != 0) {
mHandler.removeCallbacks(mRemoveCampaignBanner);
mHandler.postDelayed(mRemoveCampaignBanner, duration * 1000);
}
} catch (Exception e) {
e.printStackTrace();
Log.d(MCMCampaignDefines.LOG_TAG, "Create banner error: Attends to load the layout " + MCMCampaignDefines.RES_ID_LAYOUT);
notifyCampaignDidFail(e.getMessage());
}
}
protected static List<MCMCampaignBannerView> createBannersList(Activity activity, ArrayList<MCMCampaignDTO> campaignsArray) {
ArrayList<MCMCampaignBannerView> bannersList = new ArrayList<MCMCampaignBannerView>();
Iterator campaignsIterator = campaignsArray.iterator();
while (campaignsIterator.hasNext()) {
// Launch request to get image bitmap and add it to banner layout
// new MCMCampaignAsyncTasks.DownloadCampaignImage(this).execute((MCMCampaignDTO) campaignsIterator.next());
MCMCampaignBannerView bannerView = new MCMCampaignBannerView(activity, (MCMCampaignDTO) campaignsIterator.next());
bannersList.add(bannerView);
}
return bannersList;
}
private void createRateDialog(MCMCampaignDTO campaignDTO) {
//Only notify when the dialog will be shown
new MCMCampaignAsyncTasks.NotifyServer(activity.getApplicationContext()).execute(MCMCampaignDefines.ATTR_IMPRESSION_HIT,campaignDTO.getCampaignId());
notifyCampaignDidLoad();
MCMCampaignHelper.showRateMyAppDialog(activity, campaignDTO, new MCMCampaignHelper.RateMyAppDialogDelegate() {
@Override
public void dialogRatePressed(MCMCampaignDTO campaignDTO) {
//Open the market
Uri uri = Uri.parse("market://details?id=" + activity.getApplication().getPackageName());
activity.startActivity(new Intent(Intent.ACTION_VIEW,uri));
//Update the control parameters
MCMCampaignsLogics.updateRateDialogDontShowAgain(activity.getApplicationContext());
new MCMCampaignAsyncTasks.NotifyServer(activity.getApplicationContext()).execute(MCMCampaignDefines.ATTR_RATE_HIT,campaignDTO.getCampaignId());
notifyCampaignDidFinish();
}
@Override
public void dialogDisablePressed(MCMCampaignDTO campaignDTO) {
//Update the control parameters
MCMCampaignsLogics.updateRateDialogDontShowAgain(activity.getApplicationContext());
new MCMCampaignAsyncTasks.NotifyServer(activity.getApplicationContext()).execute(MCMCampaignDefines.ATTR_NEVER_HIT,campaignDTO.getCampaignId());
notifyCampaignDidFinish();
}
@Override
public void dialogRemindMeLaterPressed(MCMCampaignDTO campaignDTO) {
//Update the control parameters
MCMCampaignsLogics.updateRateDialogDate(activity.getApplicationContext());
new MCMCampaignAsyncTasks.NotifyServer(activity.getApplicationContext()).execute(MCMCampaignDefines.ATTR_REMIND_HIT,campaignDTO.getCampaignId());
notifyCampaignDidFinish();
}
});
}
/**
* Method that notifies the delegate that the campaign was loaded
*/
protected void notifyCampaignDidLoad() {
if (delegate != null) {
delegate.campaignDidLoad();
}
}
/**
* Method that notifies the delegate that the campaign finished
*/
protected void notifyCampaignDidFinish() {
if (delegate != null) {
delegate.campaignDidFinish();
}
}
/**
* Method that notifies the delegate that the campaign failed
*
* @param errorMessage the message of the error
*/
protected void notifyCampaignDidFail(String errorMessage) {
if (delegate != null) {
delegate.campaignDidFail(errorMessage);
}
if (receiver != null) {
receiver.onRequestFailed(errorMessage);
}
}
private static LinearLayout createPromotionsLayout(Activity activity, RelativeLayout layout) {
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
//Create the ScrollView to can add more banners
ScrollView scroll = new ScrollView(activity);
scroll.setBackgroundColor(android.R.color.transparent);
scroll.setLayoutParams(layoutParams);
LinearLayout resultantLayout = new LinearLayout(activity);
resultantLayout.setLayoutParams(layoutParams);
resultantLayout.setOrientation(LinearLayout.VERTICAL);
//Add the views in hierarchy
scroll.addView(resultantLayout);
layout.addView(scroll);
return resultantLayout;
}
private static void configureCampaignLayout(MCMCampaignDTO campaign, ViewGroup layout) {
if ( !(layout.getLayoutParams() instanceof RelativeLayout.LayoutParams) ) {
throw new IllegalArgumentException(MCMCampaignDefines.RES_ID_LAYOUT + " is not inside a RelativeLayout so it can't be placed");
}
// Config layout params depending on position
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) layout.getLayoutParams();
if (campaign.getCampaignPosition() == CampaignPosition.BOTTOM) {
params.height = MCMUtils.getDPI(layout.getContext(), MCMCampaignDefines.BANNER_SIZE_HEIGHT);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
} else if (campaign.getCampaignPosition() == CampaignPosition.TOP) {
params.height = MCMUtils.getDPI(layout.getContext(), MCMCampaignDefines.BANNER_SIZE_HEIGHT);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
} else if (campaign.getCampaignPosition() == CampaignPosition.MIDDLE_LANDSCAPE ||
campaign.getCampaignPosition() == CampaignPosition.MIDDLE_PORTRAIT) {
int margin = MCMUtils.getDPI(layout.getContext(), MCMCampaignDefines.MIDDLE_MARGIN);
layout.setPadding(margin, margin, margin, margin);
} else if (campaign.getCampaignPosition() == CampaignPosition.FULL_SCREEN) {
// mantain params of full screen
}
// Apply params to banner layout
layout.setLayoutParams(params);
}
/**
* Method that adds a close button to the layout and hide its when clicked
*
* @param layout RelativeLayout where the button will be added
*/
private void addCloseButton(final RelativeLayout layout) {
Button closeButton = new Button(layout.getContext());
closeButton.setText("X");
//Sets layout params
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
MCMUtils.getDPI(layout.getContext(),MCMCampaignDefines.CLOSE_BUTTON_SIZE),
MCMUtils.getDPI(layout.getContext(),MCMCampaignDefines.CLOSE_BUTTON_SIZE));
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, layout.getId());
params.addRule(RelativeLayout.ALIGN_PARENT_TOP, layout.getId());
//Margins
int margin = MCMUtils.getDPI(layout.getContext(),10);
params.setMargins(margin, margin, margin, margin);
closeButton.setLayoutParams(params);
//Adds the view
layout.addView(closeButton);
layout.setBackgroundColor(Color.argb(MCMCampaignDefines.BACKGROUND_ALPHA, 0, 0, 0));
//Onclick
closeButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
// Hide banner
layout.setVisibility(View.GONE);
MCMCampaignAdapter.this.notifyCampaignDidFinish();
}
});
}
private Runnable mRemoveCampaignBanner = new Runnable() {
public void run() {
mHandler.removeCallbacks(mRemoveCampaignBanner);
finishCampaign();
}
};
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
if (duration < 0) {
//sets the default time duration
this.duration = MCMCampaignDefines.DEFAULT_CAMPAIGN_DURATION;
} else {
//set the duration parameter
this.duration = duration;
}
}
public CampaignType getType() {
return type;
}
public String getCampaignResource() {
return campaignResource;
}
public String getMalcomAppId() {
return malcomAppId;
}
@Override
public void bannerDidLoad(MCMCampaignDTO campaign) {
if (delegate != null) {
delegate.campaignDidLoad();
}
}
@Override
public void bannerDidFail(String errorMessage) {
notifyCampaignDidFail(errorMessage);
}
@Override
public void bannerClosed() {
notifyCampaignDidFinish();
}
@Override
public void bannerPressed(MCMCampaignDTO campaign) {
Log.d(MCMDefines.LOG_TAG,"Banner pressed: "+campaign.getName());
if (delegate != null) {
delegate.campaignPressed(campaign);
}
}
}