Java tutorial
/* * Copyright (C) 2012 Yozio Inc. * * This file is part of the Yozio SDK. * * By using the Yozio SDK in your software, you agree to the terms of the * Yozio SDK License Agreement which can be found at www.yozio.com/sdk_license. */ package com.yozio.android; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; import java.util.UUID; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.net.ConnectivityManager; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.provider.Settings; import android.telephony.TelephonyManager; import android.util.DisplayMetrics; import android.util.Log; import android.view.WindowManager; import com.yozio.android.Yozio.GetYozioLinkCallback; import com.yozio.android.Yozio.InitializeExperimentsCallback; import com.yozio.android.YozioApiService.ExperimentInfo; import com.yozio.android.YozioDataStore.Events; class YozioHelper { // SDK version protected static final String YOZIO_SDK_VERSION = "ANDROID-v2.4"; // Android device type is 3. static final String DEVICE_TYPE = "3"; // For logging. private static final String LOGTAG = "YozioHelper"; // Event data keys. private static final String D_EVENT_TYPE = "event_type"; private static final String D_LINK_NAME = "link_name"; private static final String D_CHANNEL = "channel"; private static final String D_TIMESTAMP = "timestamp"; private static final String D_EVENT_IDENTIFIER = "event_identifier"; private static final String D_EXTERNAL_PROPERTIES = "external_properties"; // Header keys. protected static final String H_SDK_VERSION = "yozio-sdk-version"; // Payload keys. private static final String P_APP_KEY = "app_key"; private static final String P_APP_VERSION = "app_version"; private static final String P_COUNTRY_CODE = "country_code"; private static final String P_DEVICE_TYPE = "device_type"; private static final String P_HARDWARE = "hardware"; private static final String P_LANGUAGE_CODE = "language_code"; private static final String P_MAC_ADDRESS = "mac_address"; private static final String P_OPEN_UDID = "open_udid"; private static final String P_OS_VERSION = "os_version"; private static final String P_PAYLOAD = "payload"; private static final String P_USER_NAME = "external_user_id"; private static final String P_YOZIO_UDID = "yozio_udid"; private static final String P_ANDROID_ID = "android_id"; private static final String P_DEVICE_ID = "device_id"; private static final String P_DEVICE_MANUFACTURER = "device_manufacturer"; private static final String P_SERIAL_ID = "serial_id"; private static final String P_DEVICE_SCREEN_DENSITY = "device_screen_density"; private static final String P_DEVICE_SCREEN_LAYOUT_SIZE = "device_screen_layout_size"; private static final String P_CARRIER_NAME = "carrier_name"; private static final String P_CARRIER_COUNTRY_CODE = "device_model"; private static final String P_MOBILE_COUNTRY_CODE = "mobile_country_code"; private static final String P_MOBILE_NETWORK_CODE = "mobile_network_code"; private static final String P_CONNECTION_TYPE = "connection_type"; private static final String P_EXPERIMENT_VARIATION_SIDS = "experiment_variation_sids"; // Minimum number of events before flushing. private static final int FLUSH_BATCH_MIN = 1; // Maximum number of events that can be batched. private static final int FLUSH_BATCH_MAX = 50; private final YozioDataStore dataStore; private final YozioApiService apiService; private final SimpleDateFormat dateFormat; // Executor for AddEvent and Flush tasks. private final ThreadPoolExecutor executor; private JSONObject experimentConfigs; private JSONObject experimentVariationSids; private Context context; private String appKey; private String secretKey; private String userName; private String appVersion; private String countryCode; private String hardware; private String languageCode; private String macAddress; private String openUdid; private String osVersion; private String yozioUdid; private String androidId; private String deviceId; private String deviceManufacturer; private String serialId; private String deviceScreenDensity; private String deviceScreenLayoutSize; private String carrierName; private String carrierCountryCode; private String mobileCountryCode; private String mobileNetworkCode; private String connectionType; YozioHelper(YozioDataStore dataStore, YozioApiService apiService) { this.dataStore = dataStore; this.apiService = apiService; this.dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); // This executor MUST have pool size of 1 (1 task allowed to run at once). // Otherwise, we might send the same event multiple times. executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } /** * Configures the helper. */ void configure(Context context, String appKey, String secretKey) { this.context = context; this.appKey = appKey; this.secretKey = secretKey; OpenUDID.syncContext(context); this.yozioUdid = OpenUDID.getOpenUDIDInContext(); setDeviceParams(); } /** * Fetches experiment configurations Makes a blocking request */ void initializeExperiments() { printYozioUdid(); ExperimentInfo experimentInfo = apiService.getExperimentInfo(appKey, yozioUdid); this.experimentConfigs = experimentInfo.getConfigs(); this.experimentVariationSids = experimentInfo.getExperimentVariationSids(); } /** * Makes a non-blocking request to retrieve the experiment configurations. */ void initializeExperimentsAsync(InitializeExperimentsCallback callback) { printYozioUdid(); new InitializeExperimentsTask(callback).execute(); } /** * Returns an experiment configuration String for the given key */ String stringForKey(String key, String defaultValue) { try { return this.experimentConfigs.getString(key); } catch (Exception e) { return defaultValue; } } /** * Returns an experiment configuration int for the given key */ int intForKey(String key, int defaultValue) { try { return this.experimentConfigs.getInt(key); } catch (Exception e) { return defaultValue; } } /** * Sets the user name. */ void setUserName(String userName) { this.userName = userName; } /** * Validates the configuration. * * @return true iff everything is correctly configured. */ boolean validate() { if (context == null) { Log.e(LOGTAG, "context is null!"); return false; } if (appKey == null) { Log.e(LOGTAG, "appKey is null!"); return false; } if (secretKey == null) { Log.e(LOGTAG, "secretKey is null!"); return false; } return true; } /** * Makes a blocking request to retrieve the Yozio link. * * @param externalProperties meta-data Customer wants to attach to url * @return the Yozio link if the request was successful, or the destinationUrl if unsuccessful. */ String getYozioLink(String viralLoopName, String channel, String destinationUrl, JSONObject externalProperties) { String yozioLink = apiService.getYozioLink(appKey, yozioUdid, viralLoopName, destinationUrl, getYozioProperties(channel), externalProperties); return yozioLink != null ? yozioLink : destinationUrl; } /** * Makes a blocking request to retrieve the Yozio link. * * @param externalProperties meta-data Customer wants to attach to url * @return the Yozio link if the request was successful, or the destinationUrl if unsuccessful. */ String getYozioLink(String viralLoopName, String channel, String iosDestinationUrl, String androidDestinationUrl, String nonMobileDestinationUrl, JSONObject externalProperties) { String yozioLink = apiService.getYozioLink(appKey, yozioUdid, viralLoopName, iosDestinationUrl, androidDestinationUrl, nonMobileDestinationUrl, getYozioProperties(channel), externalProperties); return yozioLink != null ? yozioLink : nonMobileDestinationUrl; } /** * Makes a non-blocking request to retrieve the Yozio link. */ void getYozioLinkAsync(String viralLoopName, String channel, String destinationUrl, JSONObject externalProperties, GetYozioLinkCallback callback) { JSONObject yozioProperties = getYozioProperties(channel); new GetYozioLinkTask(viralLoopName, destinationUrl, yozioProperties, externalProperties, callback) .execute(); } /** * Makes a non-blocking request to retrieve the Yozio link. */ void getYozioLinkAsync(String viralLoopName, String channel, String iosDestinationUrl, String androidDestinationUrl, String nonMobileDestinationUrl, JSONObject externalProperties, GetYozioLinkCallback callback) { JSONObject yozioProperties = getYozioProperties(channel); new GetYozioLinkTask(viralLoopName, iosDestinationUrl, androidDestinationUrl, nonMobileDestinationUrl, yozioProperties, externalProperties, callback).execute(); } /** * Makes a non-blocking request to store the event. */ void collect(int eventType, String viralLoopName, String channel) { collect(eventType, viralLoopName, channel, null); } /** * Makes a non-blocking request to store the event. */ void collect(int eventType, String viralLoopName, String channel, JSONObject externalProperties) { JSONObject event = buildEvent(eventType, viralLoopName, channel, externalProperties); if (event == null) { return; } executor.submit(new AddEventTask(event)); } // For testing void setYozioUdid(String yozioUdid) { this.yozioUdid = yozioUdid; } // For testing String getYozioUdid() { return yozioUdid; } /** * Forces a flush attempt to the Yozio server. */ private void doFlush() { executor.submit(new FlushTask()); } private JSONObject getYozioProperties(String channel) { JSONObject yozioProperties = new JSONObject(); try { // null values are discarded by JSONObject yozioProperties.put("experiment_variation_sids", experimentVariationSids); yozioProperties.put(D_CHANNEL, channel); } catch (JSONException e) { } return yozioProperties; } private JSONObject buildEvent(int eventType, String viralLoopName, String channel, JSONObject externalProperties) { try { JSONObject eventObject = new JSONObject(); eventObject.put(D_EVENT_TYPE, eventType); eventObject.put(D_LINK_NAME, viralLoopName); eventObject.put(D_CHANNEL, channel); eventObject.put(D_TIMESTAMP, timestamp()); eventObject.put(D_EVENT_IDENTIFIER, UUID.randomUUID()); // null values are discarded by JSONObject eventObject.put(D_EXTERNAL_PROPERTIES, externalProperties); return eventObject; } catch (JSONException e) { return null; } } private String timestamp() { return dateFormat.format(Calendar.getInstance().getTime()); } private void setDeviceParams() { androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); countryCode = Locale.getDefault().getCountry(); hardware = android.os.Build.MODEL; languageCode = Locale.getDefault().getLanguage(); openUdid = OpenUDID.getOpenUDIDInContext(); osVersion = android.os.Build.VERSION.RELEASE; deviceManufacturer = android.os.Build.MANUFACTURER; setAppVersion(); setConnectionType(); setCarrierMobileAndDeviceInfo(); setMacAddress(); setScreenInfo(); } private void setAppVersion() { try { PackageManager manager = context.getPackageManager(); PackageInfo packageInfo = manager.getPackageInfo(context.getPackageName(), 0); appVersion = packageInfo.versionName; } catch (Exception e) { } } private void printYozioUdid() { // Like clutch.io, we print the Yozio device id to LogCat so developers can force experiment // variations in the UI. Log.i("Yozio", "Yozio Device Identifier (To force an experiment variation): \"" + yozioUdid + "\""); } private boolean isValidDeviceId(String deviceId) { // ---------------------------------------- // Is the device ID null or empty? // ---------------------------------------- if (deviceId == null) { return false; } // ---------------------------------------- // Is this an emulator device ID? // ---------------------------------------- else if (deviceId.length() == 0 || deviceId.equals("000000000000000") || deviceId.equals("0") || deviceId.equals("unknown")) { return false; } else { return true; } } private void setCarrierMobileAndDeviceInfo() { SharedPreferences settings = context.getSharedPreferences("yozioPreferences", 0); try { TelephonyManager telephonyManager = (TelephonyManager) context .getSystemService(Context.TELEPHONY_SERVICE); carrierName = telephonyManager.getNetworkOperatorName(); carrierCountryCode = telephonyManager.getNetworkCountryIso(); if (telephonyManager.getNetworkOperator() != null && (telephonyManager.getNetworkOperator().length() == 5 || telephonyManager.getNetworkOperator().length() == 6)) { mobileCountryCode = telephonyManager.getNetworkOperator().substring(0, 3); mobileNetworkCode = telephonyManager.getNetworkOperator().substring(3); } deviceId = telephonyManager.getDeviceId(); if (!isValidDeviceId(deviceId)) { // Fetch the emulator device ID from the preferences deviceId = settings.getString("emulatorDeviceId", null); } if (!isValidDeviceId(deviceId)) { StringBuffer buff = new StringBuffer(); buff.append("emulator"); String chars = "1234567890abcdefghijklmnopqrstuvw"; int ccLength = chars.length() - 1; for (int i = 0; i < 32; i++) { int index = (int) (Math.random() * ccLength); buff.append(chars.charAt(index)); } SharedPreferences.Editor editor = settings.edit(); editor.putString("emulatorDeviceId", deviceId); editor.commit(); deviceId = buff.toString(); } deviceId = deviceId.toLowerCase(); } catch (Exception e) { deviceId = null; } } /** * Gets the connection type used by this device ("mobile" or "wifi"). * * @return Connection type the device is using. */ private void setConnectionType() { try { // Get connection type ConnectivityManager connectivityManager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); if (connectivityManager != null && connectivityManager.getActiveNetworkInfo() != null) { switch (connectivityManager.getActiveNetworkInfo().getType()) { case ConnectivityManager.TYPE_WIFI: case 0x6: // ConnectivityManager.TYPE_WIMAX connectionType = "wifi"; break; default: connectionType = "mobile"; break; } } } catch (Exception e) { } } // Get screen density and layout private void setScreenInfo() { try { // This is a backwards compatibility fix for Android 1.5 which has no // display metric API. // If this is 1.6 or higher, then load the class, otherwise the class // never loads and // no crash occurs. if (Integer.parseInt(android.os.Build.VERSION.SDK) > 3) { DisplayMetrics displayMetrics = new DisplayMetrics(); WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); windowManager.getDefaultDisplay().getMetrics(displayMetrics); Configuration configuration = context.getResources().getConfiguration(); deviceScreenDensity = "" + displayMetrics.densityDpi; deviceScreenLayoutSize = "" + (configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK); } } catch (Exception e) { } } private void setMacAddress() { try { WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); if (wifiManager != null) { WifiInfo wifiInfo = wifiManager.getConnectionInfo(); if (wifiInfo != null) { macAddress = wifiInfo.getMacAddress(); } } } catch (Exception e) { } } private class InitializeExperimentsTask extends AsyncTask<Void, Void, ExperimentInfo> { private final InitializeExperimentsCallback callback; InitializeExperimentsTask(InitializeExperimentsCallback callback) { this.callback = callback; } @Override protected ExperimentInfo doInBackground(Void... params) { return apiService.getExperimentInfo(appKey, yozioUdid); } @Override protected void onPostExecute(ExperimentInfo experimentInfo) { experimentConfigs = experimentInfo.getConfigs(); experimentVariationSids = experimentInfo.getExperimentVariationSids(); callback.onComplete(); } } private class GetYozioLinkTask extends AsyncTask<Void, Void, String> { private final String viralLoopName; private final String destinationUrl; private final String iosDestinationUrl; private final String androidDestinationUrl; private final String nonMobileDestinationUrl; private final JSONObject externalProperties; private final JSONObject yozioProperties; private final GetYozioLinkCallback callback; GetYozioLinkTask(String viralLoopName, String destinationUrl, JSONObject yozioProperties, JSONObject externalProperties, GetYozioLinkCallback callback) { this.viralLoopName = viralLoopName; this.iosDestinationUrl = null; this.androidDestinationUrl = null; this.nonMobileDestinationUrl = null; this.destinationUrl = destinationUrl; this.externalProperties = externalProperties; this.yozioProperties = yozioProperties; this.callback = callback; } GetYozioLinkTask(String viralLoopName, String iosDestinationUrl, String androidDestinationUrl, String nonMobileDestinationUrl, JSONObject yozioProperties, JSONObject externalProperties, GetYozioLinkCallback callback) { this.viralLoopName = viralLoopName; this.iosDestinationUrl = iosDestinationUrl; this.androidDestinationUrl = androidDestinationUrl; this.nonMobileDestinationUrl = nonMobileDestinationUrl; this.destinationUrl = null; this.externalProperties = externalProperties; this.yozioProperties = yozioProperties; this.callback = callback; } @Override protected String doInBackground(Void... arg0) { if (destinationUrl != null) { return apiService.getYozioLink(appKey, yozioUdid, viralLoopName, destinationUrl, yozioProperties, externalProperties); } else { return apiService.getYozioLink(appKey, yozioUdid, viralLoopName, iosDestinationUrl, androidDestinationUrl, nonMobileDestinationUrl, yozioProperties, externalProperties); } } @Override protected void onPostExecute(String yozioLink) { callback.handleResponse(yozioLink != null ? yozioLink : destinationUrl); } } /** * Task to add an event to the data store. Will try to flush if there are * enough events stored. */ private class AddEventTask implements Runnable { private final JSONObject event; AddEventTask(JSONObject event) { this.event = event; } public void run() { boolean eventAdded = dataStore.addEvent(event); // Flush if there are enough events. // Small optimization for the special case where FLUSH_BATCH_MIN == 1. boolean flushEligible = (eventAdded && FLUSH_BATCH_MIN == 1) || (dataStore.getNumEvents() >= FLUSH_BATCH_MIN); if (flushEligible) { doFlush(); } } } /** * Task to flush the data through the {@link YozioApiService}. */ private class FlushTask implements Runnable { public void run() { final Events events = dataStore.getEvents(FLUSH_BATCH_MAX); if (events == null) { return; } JSONObject payload = buildPayload(events.getJsonArray()); if (payload == null) { return; } if (apiService.batchEvents(payload)) { dataStore.removeEvents(events.getLastEventId()); } } private JSONObject buildPayload(JSONArray events) { try { JSONObject payloadObject = new JSONObject(); payloadObject.put(P_APP_KEY, appKey); payloadObject.put(P_USER_NAME, userName); payloadObject.put(P_DEVICE_TYPE, DEVICE_TYPE); payloadObject.put(P_YOZIO_UDID, yozioUdid); payloadObject.put(P_PAYLOAD, events); payloadObject.put(P_APP_VERSION, appVersion); payloadObject.put(P_COUNTRY_CODE, countryCode); payloadObject.put(P_HARDWARE, hardware); payloadObject.put(P_LANGUAGE_CODE, languageCode); payloadObject.put(P_MAC_ADDRESS, macAddress); payloadObject.put(P_OPEN_UDID, openUdid); payloadObject.put(P_OS_VERSION, osVersion); payloadObject.put(P_ANDROID_ID, androidId); payloadObject.put(P_DEVICE_ID, deviceId); payloadObject.put(P_DEVICE_MANUFACTURER, deviceManufacturer); payloadObject.put(P_SERIAL_ID, serialId); payloadObject.put(P_DEVICE_SCREEN_DENSITY, deviceScreenDensity); payloadObject.put(P_DEVICE_SCREEN_LAYOUT_SIZE, deviceScreenLayoutSize); payloadObject.put(P_CARRIER_NAME, carrierName); payloadObject.put(P_CARRIER_COUNTRY_CODE, carrierCountryCode); payloadObject.put(P_MOBILE_COUNTRY_CODE, mobileCountryCode); payloadObject.put(P_MOBILE_NETWORK_CODE, mobileNetworkCode); payloadObject.put(P_CONNECTION_TYPE, connectionType); // null values are discarded by JSONObject payloadObject.put(P_EXPERIMENT_VARIATION_SIDS, experimentVariationSids); return payloadObject; } catch (JSONException e) { return null; } } } }