com.adflake.AdFlakeManager.java Source code

Java tutorial

Introduction

Here is the source code for com.adflake.AdFlakeManager.java

Source

/**
 * AdFlakeManager.java (AdFlakeSDK-Android)
 *
 * Copyright  2013 MADE GmbH - All Rights Reserved.
 *
 * Unauthorized copying of this file, via any medium is strictly prohibited
 * unless otherwise noted in the License section of this document header.
 *
 * @file AdFlakeManager.java
 * @copyright 2013 MADE GmbH. All rights reserved.
 * @section License
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.adflake;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Random;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationManager;
import android.provider.Settings.Secure;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;

import com.adflake.adapters.AdColonyVideoAdsAdapter;
import com.adflake.obj.Custom;
import com.adflake.obj.Extra;
import com.adflake.obj.Ration;
import com.adflake.util.AdFlakeUtil;

/**
 * The AdFlakeManager class manages the AdFlake configuration. If necessary the
 * class will download it from a remote server.
 */
public class AdFlakeManager {
    public String adFlakeKey;

    public String localeString;
    public String deviceIDHash;

    public boolean sleeperMode;
    public boolean videoAdsAvailable;

    public Location location;

    /**
     * Instantiates a new ad flake manager.
     * 
     * @param contextReference
     *            the context reference
     * @param adFlakeKey
     *            the ad flake key
     */
    public AdFlakeManager(WeakReference<Context> contextReference, String adFlakeKey) {
        Log.i(AdFlakeUtil.ADFLAKE, "Creating adFlakeManager...");
        this._contextReference = contextReference;
        this.adFlakeKey = adFlakeKey;

        this.sleeperMode = false;

        localeString = Locale.getDefault().toString();
        Log.d(AdFlakeUtil.ADFLAKE, "Locale is: " + localeString);

        MessageDigest md;
        try {
            md = MessageDigest.getInstance("MD5");
            StringBuffer deviceIDString = new StringBuffer(Secure.ANDROID_ID);
            deviceIDString.append("AdFlake");
            deviceIDHash = AdFlakeUtil.convertToHex(md.digest(deviceIDString.toString().getBytes()));
        } catch (NoSuchAlgorithmException e) {
            deviceIDHash = "00000000000000000000000000000000";
        }
        Log.d(AdFlakeUtil.ADFLAKE, "Hashed device ID is: " + deviceIDHash);

        Log.i(AdFlakeUtil.ADFLAKE, "Finished creating adFlakeManager");
    }

    /**
     * Sets the config expire timeout.
     * 
     * @param configExpireTimeout
     *            the new config expire timeout
     */
    public static void setConfigExpireTimeout(long configExpireTimeout) {
        AdFlakeManager._configExpireTimeout = configExpireTimeout;
    }

    /**
     * Gets the extra.
     * 
     * @return the extra
     */
    public Extra getExtra() {
        if (_totalWeight <= 0) {
            return null;
        } else {
            return this._extra;
        }
    }

    /**
     * Gets the darted ration.
     * 
     * @return the darted ration
     */
    public Ration getDartedRation() {
        Random random = new Random();

        double r = random.nextDouble() * _totalWeight;
        double s = 0;

        Log.d(AdFlakeUtil.ADFLAKE, "Dart is <" + r + "> of <" + _totalWeight + ">");

        Iterator<Ration> it = this._rationsList.iterator();
        Ration ration = null;
        while (it.hasNext()) {
            ration = it.next();
            s += ration.weight;

            if (s >= r) {
                break;
            }
        }

        return ration;
    }

    /**
     * Gets the ration for current rollover list position.
     * 
     * @return the ration for current rollover list position
     */
    public Ration getRationForCurrentRolloverListPosition() {
        if (this._rollovers == null) {
            return null;
        }

        Ration ration = null;
        if (this._rollovers.hasNext()) {
            ration = this._rollovers.next();
        }

        return ration;
    }

    /**
     * Reset rollover list pointer to the beginning of the list.
     */
    public void resetRollover() {
        this._rollovers = this._rationsList.iterator();
    }

    /**
     * Fetch a custom banner from server with the specifeid network id.
     * 
     * @param nid
     *            the nid
     * @return the custom
     */
    public Custom fetchCustomBannerFromServerWithNetworkID(String nid) {
        HttpClient httpClient = new DefaultHttpClient();

        String locationString;
        if (_extra.locationOn == 1) {
            location = getCurrentLocation();
            if (location != null) {
                locationString = String.format(AdFlakeUtil.locationString, location.getLatitude(),
                        location.getLongitude(), location.getTime());
            } else {
                locationString = "";
            }
        } else {
            location = null;
            locationString = "";
        }

        String url = String.format(AdFlakeUtil.urlCustom, this.adFlakeKey, nid, deviceIDHash, localeString,
                locationString, AdFlakeUtil.VERSION);
        HttpGet httpGet = new HttpGet(url);

        HttpResponse httpResponse;
        try {
            httpResponse = httpClient.execute(httpGet);

            Log.d(AdFlakeUtil.ADFLAKE, httpResponse.getStatusLine().toString());

            HttpEntity entity = httpResponse.getEntity();

            if (entity != null) {
                InputStream inputStream = entity.getContent();
                String jsonString = convertStreamToString(inputStream);
                return parseCustomJsonString(jsonString);
            }
        } catch (ClientProtocolException e) {
            Log.e(AdFlakeUtil.ADFLAKE, "Caught ClientProtocolException in getCustom()", e);
        } catch (IOException e) {
            Log.e(AdFlakeUtil.ADFLAKE, "Caught IOException in getCustom()", e);
        }

        return null;
    }

    /**
     * Fetch the ADFlake config from the remote server.
     * 
     * @note If the config timeout has not been hit, the previously downloaded
     *       config will be reused. If testmode is enabled, the config will
     *       always be downloaded.
     */
    public void fetchConfigFromServer() {
        Context context = _contextReference.get();

        // If the context is null here something went wrong with initialization.
        if (context == null) {
            return;
        }

        SharedPreferences adFlakePrefs = context.getSharedPreferences(adFlakeKey, Context.MODE_PRIVATE);
        String jsonString = adFlakePrefs.getString(PREFS_STRING_CONFIG, null);
        long timestamp = adFlakePrefs.getLong(PREFS_STRING_TIMESTAMP, -1);

        Log.d(AdFlakeUtil.ADFLAKE, "Prefs{" + adFlakeKey + "}: {\"" + PREFS_STRING_CONFIG + "\": \"" + jsonString
                + "\", \"" + PREFS_STRING_TIMESTAMP + "\": " + timestamp + "}");

        if (jsonString == null || _configExpireTimeout == -1
                || System.currentTimeMillis() >= timestamp + _configExpireTimeout
                || AdFlakeTargeting.getTestMode() == true) {
            Log.i(AdFlakeUtil.ADFLAKE, "Stored config info not present or expired, fetching fresh data");

            HttpClient httpClient = new DefaultHttpClient();

            String url = String.format(AdFlakeUtil.urlConfig, this.adFlakeKey, AdFlakeUtil.VERSION);
            HttpGet httpGet = new HttpGet(url);

            HttpResponse httpResponse;
            try {
                httpResponse = httpClient.execute(httpGet);

                Log.d(AdFlakeUtil.ADFLAKE, httpResponse.getStatusLine().toString());

                HttpEntity entity = httpResponse.getEntity();

                if (entity != null) {
                    InputStream inputStream = entity.getContent();
                    jsonString = convertStreamToString(inputStream);

                    SharedPreferences.Editor editor = adFlakePrefs.edit();
                    editor.putString(PREFS_STRING_CONFIG, jsonString);
                    editor.putLong(PREFS_STRING_TIMESTAMP, System.currentTimeMillis());
                    editor.commit();
                }
            } catch (ClientProtocolException e) {
                Log.e(AdFlakeUtil.ADFLAKE, "Caught ClientProtocolException in fetchConfig()", e);
            } catch (IOException e) {
                Log.e(AdFlakeUtil.ADFLAKE, "Caught IOException in fetchConfig()", e);
            }
        } else {
            Log.i(AdFlakeUtil.ADFLAKE, "Using stored config data");
        }

        parseConfigurationString(jsonString);
    }

    /**
     * Convert stream to string.
     * 
     * @param is
     *            the is
     * @return the string
     */
    private String convertStreamToString(InputStream is) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is), 8192);
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            Log.e(AdFlakeUtil.ADFLAKE, "Caught IOException in convertStreamToString()", e);
            return null;
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                Log.e(AdFlakeUtil.ADFLAKE, "Caught IOException in convertStreamToString()", e);
                return null;
            }
        }

        return sb.toString();
    }

    /**
     * Parses the configuration section from the specified JSON string.
     * 
     * @param jsonString
     *            the json string
     */
    private void parseConfigurationString(String jsonString) {
        Log.d(AdFlakeUtil.ADFLAKE, "Received jsonString: " + jsonString);

        try {
            JSONObject json = new JSONObject(jsonString);

            parseExtraJson(json.getJSONObject("extra"));
            parseRationsJson(json.getJSONArray("rations"));
            parseVideoRationsJson(json.getJSONArray("videoRations"));
        } catch (JSONException e) {
            Log.e(AdFlakeUtil.ADFLAKE, "Unable to parse response from JSON. This may or may not be fatal.", e);
            this._extra = new Extra();
        } catch (NullPointerException e) {
            Log.e(AdFlakeUtil.ADFLAKE, "Unable to parse response from JSON. This may or may not be fatal.", e);
            this._extra = new Extra();
        }
    }

    /**
     * Parses the extra section from the specified json object.
     * 
     * @param json
     *            the json
     */
    private void parseExtraJson(JSONObject json) {
        Extra extra = new Extra();

        try {
            extra.cycleTime = json.getInt("cycle_time");
            extra.locationOn = json.getInt("location_on");
            extra.transition = json.getInt("transition");

            // Due to legacy clients, the server reports alpha on a scale of
            // 0.0-1.0
            // instead of 0-255

            JSONObject backgroundColor = json.getJSONObject("background_color_rgb");
            extra.bgRed = backgroundColor.getInt("red");
            extra.bgGreen = backgroundColor.getInt("green");
            extra.bgBlue = backgroundColor.getInt("blue");
            extra.bgAlpha = backgroundColor.getInt("alpha") * 255;

            JSONObject textColor = json.getJSONObject("text_color_rgb");
            extra.fgRed = textColor.getInt("red");
            extra.fgGreen = textColor.getInt("green");
            extra.fgBlue = textColor.getInt("blue");
            extra.fgAlpha = textColor.getInt("alpha") * 255;
        } catch (JSONException e) {
            Log.e(AdFlakeUtil.ADFLAKE, "Exception in parsing config.extra JSON. This may or may not be fatal.", e);
        }

        this._extra = extra;
    }

    /**
     * Parses the rations section from the specified json object.
     * 
     * @param json
     *            the json
     */
    private void parseRationsJson(JSONArray json) {
        List<Ration> rationsList = new ArrayList<Ration>();

        this._totalWeight = 0;

        try {
            int i;
            for (i = 0; i < json.length(); i++) {
                JSONObject jsonRation = json.getJSONObject(i);
                if (jsonRation == null) {
                    continue;
                }

                Ration ration = new Ration();

                ration.nid = jsonRation.getString("nid");
                ration.type = jsonRation.getInt("type");
                ration.name = jsonRation.getString("nname");
                ration.weight = jsonRation.getInt("weight");
                ration.priority = jsonRation.getInt("priority");

                // Quattro has a special key format due to legacy compatibility.
                switch (ration.type) {
                case AdFlakeUtil.NETWORK_TYPE_QUATTRO:
                    JSONObject keyObj = jsonRation.getJSONObject("key");
                    ration.key = keyObj.getString("siteID");
                    ration.key2 = keyObj.getString("publisherID");
                    break;

                case AdFlakeUtil.NETWORK_TYPE_NEXAGE:
                    keyObj = jsonRation.getJSONObject("key");
                    ration.key = keyObj.getString("dcn");
                    ration.key2 = keyObj.getString("position");
                    break;

                default:
                    ration.key = jsonRation.getString("key");
                    break;
                }

                this._totalWeight += ration.weight;

                rationsList.add(ration);
            }
        } catch (JSONException e) {
            Log.e(AdFlakeUtil.ADFLAKE,
                    "JSONException in parsing config.rations JSON. This may or may not be fatal.", e);
        }

        Collections.sort(rationsList);

        if (this._totalWeight <= 0) {
            Log.i(AdFlakeUtil.ADFLAKE, "Sum of ration weights is 0 - no ads to be shown, sleeper mode enabled");
            this.sleeperMode = true;
        } else {
            this.sleeperMode = false;
        }

        this._rationsList = rationsList;
        this._rollovers = this._rationsList.iterator();
    }

    /**
     * Parses the rations section from the specified json object.
     * 
     * @param json
     *            the json
     */
    private void parseVideoRationsJson(JSONArray json) {
        List<Ration> rationsList = new ArrayList<Ration>();

        _totalVideoWeight = 0;

        try {
            int i;
            for (i = 0; i < json.length(); i++) {
                JSONObject jsonRation = json.getJSONObject(i);
                if (jsonRation == null) {
                    continue;
                }

                Ration ration = new Ration();

                ration.nid = jsonRation.getString("nid");
                ration.type = jsonRation.getInt("type");
                ration.name = jsonRation.getString("nname");
                ration.weight = jsonRation.getInt("weight");
                ration.priority = jsonRation.getInt("priority");

                switch (ration.type) {
                case AdFlakeUtil.NETWORK_TYPE_BEACHFRONT:
                case AdFlakeUtil.NETWORK_TYPE_ADCOLONY:
                    ration.key = jsonRation.getString("key");

                    int zoneIndex = ration.key.indexOf("|;|");
                    if (zoneIndex < 0) {
                        Log.w(AdFlakeUtil.ADFLAKE, "key separator not found for network=" + ration.name);
                        continue;
                    }

                    ration.key2 = ration.key.substring(zoneIndex + 3);
                    ration.key = ration.key.substring(0, zoneIndex);
                    break;

                default:
                    ration.key = jsonRation.getString("key");
                    break;
                }

                this._totalVideoWeight += ration.weight;

                rationsList.add(ration);
            }
        } catch (JSONException e) {
            Log.e(AdFlakeUtil.ADFLAKE,
                    "JSONException in parsing config.rations JSON. This may or may not be fatal.", e);
        }

        Collections.sort(rationsList);

        if (_totalVideoWeight <= 0) {
            Log.i(AdFlakeUtil.ADFLAKE, "Sum of video ration weights is 0 - no video ads available");
            this.videoAdsAvailable = false;
        } else {
            this.videoAdsAvailable = true;
        }

        this._videoRationsList = rationsList;
    }

    /**
     * Parses the custom section from the specified json string.
     * 
     * @param jsonString
     *            the json string
     * @return the custom
     */
    private Custom parseCustomJsonString(String jsonString) {
        Log.d(AdFlakeUtil.ADFLAKE, "Received custom jsonString: " + jsonString);

        Custom custom = new Custom();
        try {
            JSONObject json = new JSONObject(jsonString);

            custom.type = json.getInt("ad_type");
            custom.imageLink = json.getString("img_url");
            custom.link = json.getString("redirect_url");
            custom.description = json.getString("ad_text");

            try {
                custom.imageLink640x100 = json.getString("img_url_640x100");
            } catch (JSONException e) {
                custom.imageLink640x100 = null;
            }
            try {
                custom.imageLink480x75 = json.getString("img_url_480x75");
            } catch (JSONException e) {
                custom.imageLink480x75 = null;
            }

            DisplayMetrics metrics = new DisplayMetrics();
            ((WindowManager) _contextReference.get().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
                    .getMetrics(metrics);

            if (metrics.density >= 2.0 && custom.type == AdFlakeUtil.CUSTOM_TYPE_BANNER
                    && custom.imageLink640x100 != null && custom.imageLink640x100.length() != 0) {
                custom.image = fetchImageWithURL(custom.imageLink640x100);
            } else if (metrics.density >= 1.5 && custom.type == AdFlakeUtil.CUSTOM_TYPE_BANNER
                    && custom.imageLink480x75 != null && custom.imageLink480x75.length() != 0) {
                custom.image = fetchImageWithURL(custom.imageLink480x75);
            } else {
                custom.image = fetchImageWithURL(custom.imageLink);
            }
        } catch (JSONException e) {
            Log.e(AdFlakeUtil.ADFLAKE, "Caught JSONException in parseCustomJsonString()", e);
            return null;
        }

        return custom;
    }

    /**
     * Fetch image with the specified url.
     * 
     * @param urlString
     *            the url string
     * @return the drawable
     */
    private Drawable fetchImageWithURL(String urlString) {
        try {
            URL url = new URL(urlString);
            InputStream is = (InputStream) url.getContent();
            Drawable d = Drawable.createFromStream(is, "src");
            return d;
        } catch (Exception e) {
            Log.e(AdFlakeUtil.ADFLAKE, "Unable to fetchImage(): ", e);
            return null;
        }
    }

    /**
     * Gets the current location.
     * 
     * @note If location is not enabled, this method will return null
     * @return the current location or null if location access is not enabled or
     *         granted by the user.
     */
    public Location getCurrentLocation() {
        if (_contextReference == null) {
            return null;
        }

        Context context = _contextReference.get();
        if (context == null) {
            return null;
        }

        Location location = null;

        if (context.checkCallingOrSelfPermission(
                android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
            location = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        } else if (context.checkCallingOrSelfPermission(
                android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
            location = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
        }
        return location;
    }

    public Ration getNextDartedVideoRation(List<Ration> usedVideoRations) {
        Random random = new Random();

        double actualWeight = 0;

        for (Ration ration : _videoRationsList) {
            if (usedVideoRations.contains(ration))
                continue;

            actualWeight += ration.weight;
        }

        double r = random.nextDouble() * actualWeight;
        double s = 0;

        Log.d(AdFlakeUtil.ADFLAKE,
                "Dart is <" + r + "> of <" + actualWeight + "> and total <" + _totalWeight + ">");

        Iterator<Ration> it = this._videoRationsList.iterator();
        Ration ration = null;
        while (it.hasNext()) {
            ration = it.next();

            if (usedVideoRations.contains(ration))
                continue;

            s += ration.weight;

            if (s >= r) {
                break;
            }
        }

        return ration;
    }

    public int getVideoRationCount() {
        return _videoRationsList.size();
    }

    public void prepareVideoAdaptersForLayout(AdFlakeLayout adFlakeLayout) {
        Log.d(AdFlakeUtil.ADFLAKE, "prepareVideoAdaptersForLayout");

        for (Ration ration : _videoRationsList) {
            try {
                switch (ration.type) {
                case AdFlakeUtil.NETWORK_TYPE_ADCOLONY:
                    AdColonyVideoAdsAdapter.prepareForRation(ration, adFlakeLayout);
                    break;

                default:
                    break;
                }
            } catch (Throwable ex) {
                Log.e(AdFlakeUtil.ADFLAKE, "prepareVideoAdaptersForLayout failed to prepare for ration="
                        + ration.name + "\n error=" + ex.toString());
            }
        }
    }

    private Extra _extra;
    private List<Ration> _rationsList;
    private List<Ration> _videoRationsList;
    private double _totalWeight = 0;
    private double _totalVideoWeight = 0;
    private WeakReference<Context> _contextReference;

    private Iterator<Ration> _rollovers;

    /** Default config expire timeout is 30 minutes. */
    private static long _configExpireTimeout = 1800000;

    private final static String PREFS_STRING_TIMESTAMP = "timestamp";
    private final static String PREFS_STRING_CONFIG = "config";
}