diff --git a/build.gradle b/build.gradle index e0b366a78..90cf99341 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,16 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { repositories { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' - + classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' + classpath "com.github.dcendents:android-maven-gradle-plugin:1.3" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } + } allprojects { @@ -20,4 +21,4 @@ allprojects { task clean(type: Delete) { delete rootProject.buildDir -} +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f23df6e46..0034d64c7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Oct 21 11:34:03 PDT 2015 +#Wed Jun 01 13:16:14 PDT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip diff --git a/iterableapi/build.gradle b/iterableapi/build.gradle index b307991e0..de08c9325 100644 --- a/iterableapi/build.gradle +++ b/iterableapi/build.gradle @@ -22,4 +22,34 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.2.1' + compile 'com.google.android.gms:play-services-gcm:7.5.0' + compile 'com.android.support:support-annotations:23.2.1' } + +ext { + bintrayRepo = 'maven' + bintrayName = 'Iterable-SDK' + + publishedGroupId = 'com.iterable' + libraryName = 'iterableapi' + artifact = 'iterableapi' + + libraryDescription = 'Iterable Android SDK' + + siteUrl = 'https://github.com/Iterable/iterable-android-sdk' + gitUrl = 'https://github.com/Iterable/iterable-android-sdk.git' + + libraryVersion = '1.2016.06.01' + + developerId = 'davidtruong' + developerName = 'David Truong' + developerEmail = 'dt@iterable.com' + + licenseName = 'The Apache Software License, Version 2.0' + licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + allLicenses = ["Apache-2.0"] +} + + +apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' +apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' diff --git a/iterableapi/src/main/AndroidManifest.xml b/iterableapi/src/main/AndroidManifest.xml index e373ded3f..0457722a6 100644 --- a/iterableapi/src/main/AndroidManifest.xml +++ b/iterableapi/src/main/AndroidManifest.xml @@ -2,7 +2,6 @@ package="iterable.com.iterableapi"> diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java index 6a9a20203..96baf1f8e 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java @@ -1,34 +1,390 @@ package com.iterable.iterableapi; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; import android.util.Log; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + /** - * Created by davidtruong on 4/4/16. + * Created by David Truong dt@iterable.com */ public class IterableApi { - static IterableApi sharedInstance = null; + static final String TAG = "IterableApi"; + static final String NOTIFICATION_ICON_NAME = "iterable_notification_icon"; + + protected static IterableApi sharedInstance = null; + + private Context _context; + private String _apiKey; + private String _email; + + private Bundle _payloadData; + private IterableNotificationData _notificationData; + private String _pushToken; + + private IterableApi(Context context, String apiKey, String email){ + updateData(context, apiKey, email); + } - //Singleton - public static IterableApi sharedInstanceWithApiKey(String apikey, String email) + /** + * Returns a shared instance of IterableApi. Updates the client data if an instance already exists. + * Should be called whenever the app is opened. + * @param context The current activity + * @return stored instance of IterableApi + */ + public static IterableApi sharedInstanceWithApiKey(Context context, String apiKey, String email) { - //TODO: what if the app is already running and the notif is pressed? if (sharedInstance == null) { - sharedInstance = new IterableApi(); - //Create instance - //sharedInstance.trackPushOpen(); + sharedInstance = new IterableApi(context, apiKey, email); + } else{ + sharedInstance.updateData(context, apiKey, email); + } + + if (context instanceof Activity) { + Activity currentActivity = (Activity) context; + Intent calledIntent = currentActivity.getIntent(); + sharedInstance.tryTrackNotifOpen(calledIntent); + } + else { + Log.d(TAG, "Notification Opens will not be tracked: "+ + "sharedInstanceWithApiKey called with a Context that is not an instance of Activity. " + + "Pass in an Activity to IterableApi.sharedInstanceWithApiKey to enable open tracking."); } return sharedInstance; } - public static void connect() { - Log.d("test", "msg"); + private void updateData(Context context, String apiKey, String email) { + this._context = context; + this._apiKey = apiKey; + this._email = email; + } + + protected Context getMainActivityContext() { + return _context; + } + + /** + * Sets the icon to be displayed in notifications. + * The icon name should match the resource name stored in the /res/drawable directory. + * @param iconName + */ + public void setNotificationIcon(String iconName) { + setNotificationIcon(_context, iconName); + } + + protected void setPushToken(String token) { _pushToken = token; } + + protected static void setNotificationIcon(Context context, String iconName) { + SharedPreferences sharedPref = ((Activity) context).getSharedPreferences(NOTIFICATION_ICON_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPref.edit(); + editor.putString(NOTIFICATION_ICON_NAME, iconName); + editor.commit(); + } + + protected static String getNotificationIcon(Context context) { + SharedPreferences sharedPref = context.getSharedPreferences(NOTIFICATION_ICON_NAME, Context.MODE_PRIVATE); + String iconName = sharedPref.getString(NOTIFICATION_ICON_NAME, ""); + return iconName; + } + + /** + * Automatically generates a GCM token and registers it with Iterable. + * @param iterableAppId The applicationId of the Iterable Push Integration + * - https://app.iterable.com/integrations/mobilePush + * @param gcmProjectId The Google Project Number + * - https://console.developers.google.com/iam-admin/settings + */ + public void registerForPush(String iterableAppId, String gcmProjectId) { + Intent pushRegistrationIntent = new Intent(_context, IterablePushReceiver.class); + pushRegistrationIntent.setAction(IterableConstants.ACTION_PUSH_REGISTRATION); + pushRegistrationIntent.putExtra(IterableConstants.PUSH_APPID, iterableAppId); + pushRegistrationIntent.putExtra(IterableConstants.PUSH_PROJECTID, gcmProjectId); + _context.sendBroadcast(pushRegistrationIntent); + } + + private void tryTrackNotifOpen(Intent calledIntent) { + Bundle extras = calledIntent.getExtras(); + if (extras != null) { + Intent intent = new Intent(); + intent.setClass(_context, IterablePushOpenReceiver.class); + intent.setAction(IterableConstants.ACTION_NOTIF_OPENED); + intent.putExtras(extras); + _context.sendBroadcast(intent); + } + } + + /** + * Registers an existing GCM device token with Iterable. + * Recommended to use registerForPush if you do not already have a deviceToken + * @param applicationName + * @param token + */ + public void registerDeviceToken(String applicationName, String token) { + registerDeviceToken(applicationName, token, null); + } + + /** + * Registers the GCM registration ID with Iterable. + * @param applicationName + * @param token + * @param dataFields + */ + private void registerDeviceToken(String applicationName, String token, JSONObject dataFields) { + String platform = IterableConstants.MESSAGING_PLATFORM_GOOGLE; + + JSONObject requestJSON = new JSONObject(); + try { + requestJSON.put(IterableConstants.KEY_EMAIL, _email); + if (dataFields == null) { + dataFields = new JSONObject(); + } + + JSONObject device = new JSONObject(); + device.put(IterableConstants.KEY_TOKEN, token); + device.put(IterableConstants.KEY_PLATFORM, platform); + device.put(IterableConstants.KEY_APPLICATIONNAME, applicationName); + device.put(IterableConstants.KEY_DATAFIELDS, dataFields); + requestJSON.put(IterableConstants.KEY_DEVICE, device); + + } catch (JSONException e) { + e.printStackTrace(); + } + + sendRequest(IterableConstants.ENDPOINT_REGISTERDEVICETOKEN, requestJSON); + } + + public void track(String eventName) { + track(eventName, null, null, null); + } + + public void track(String eventName, JSONObject dataFields) { + track(eventName, null, null, dataFields); + } + + public void track(String eventName, String campaignId, String templateId) { + track(eventName, campaignId, templateId, null); + } + + public void track(String eventName, String campaignId, String templateId, JSONObject dataFields) { + JSONObject requestJSON = new JSONObject(); + try { + requestJSON.put(IterableConstants.KEY_EMAIL, _email); + requestJSON.put(IterableConstants.KEY_EVENTNAME, eventName); + + requestJSON.put(IterableConstants.KEY_CAMPAIGNID, campaignId); + requestJSON.put(IterableConstants.KEY_TEMPLATE_ID, templateId); + requestJSON.put(IterableConstants.KEY_DATAFIELDS, dataFields); + } + catch (JSONException e) { + e.printStackTrace(); + } + + sendRequest(IterableConstants.ENDPOINT_TRACK, requestJSON); + } + + public void trackConversion(int campaignId, int templateId) { + trackConversion(campaignId, templateId, null); + } + + public void trackConversion(int campaignId, int templateId, JSONObject dataFields) { + + JSONObject requestJSON = new JSONObject(); + + try { + requestJSON.put(IterableConstants.KEY_EMAIL, _email); + requestJSON.put(IterableConstants.KEY_CAMPAIGNID, campaignId); + requestJSON.put(IterableConstants.KEY_TEMPLATE_ID, templateId); + if (dataFields != null) { + requestJSON.put(IterableConstants.KEY_DATAFIELDS, dataFields); + } + } + catch (JSONException e) { + e.printStackTrace(); + } + + sendRequest(IterableConstants.ENDPOINT_TRACKCONVERSION, requestJSON); + } + + /** + * Track when a push notification is opened on device. + * @param campaignId + * @param templateId + */ + protected void trackPushOpen(int campaignId, int templateId) { + JSONObject requestJSON = new JSONObject(); + + try { + requestJSON.put(IterableConstants.KEY_EMAIL, _email); + requestJSON.put(IterableConstants.KEY_CAMPAIGNID, campaignId); + requestJSON.put(IterableConstants.KEY_TEMPLATE_ID, templateId); + + } + catch (JSONException e) { + e.printStackTrace(); + } + + sendRequest(IterableConstants.ENDPOINT_TRACKPUSHOPEN, requestJSON); + } + + public void sendPush(String email, int campaignId) { + sendPush(email, campaignId, null, null); + } + + /** + * Sends a push campaign to an email address at the given time. + * @param sendAt Schedule the message for up to 365 days in the future. + * If set in the past, message is sent immediately. + * Format is YYYY-MM-DD HH:MM:SS in UTC + */ + public void sendPush(String email, int campaignId, Date sendAt) { + sendPush(email, campaignId, sendAt, null); + } + + public void sendPush(String email, int campaignId, JSONObject dataFields) { + sendPush(email, campaignId, null, dataFields); + } + + /** + * Sends a push campaign to an email address at the given time. + * @param sendAt Schedule the message for up to 365 days in the future. + * If set in the past, message is sent immediately. + * Format is YYYY-MM-DD HH:MM:SS in UTC + */ + public void sendPush(String email, int campaignId, Date sendAt, JSONObject dataFields) { + JSONObject requestJSON = new JSONObject(); + + try { + requestJSON.put(IterableConstants.KEY_RECIPIENT_EMAIL, email); + requestJSON.put(IterableConstants.KEY_CAMPAIGNID, campaignId); + if (sendAt != null){ + String DATEFORMAT = "yyyy-MM-dd HH:mm:ss"; + SimpleDateFormat sdf = new SimpleDateFormat(DATEFORMAT); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + String dateString = sdf.format(sendAt); + requestJSON.put(IterableConstants.KEY_SEND_AT, dateString); + } + } + catch (JSONException e) { + e.printStackTrace(); + } + + sendRequest(IterableConstants.ENDPOINT_PUSHTARGET, requestJSON); + } + + public void updateEmail(String newEmail) { + JSONObject requestJSON = new JSONObject(); + + try { + requestJSON.put(IterableConstants.KEY_CURRENT_EMAIL, _email); + requestJSON.put(IterableConstants.KEY_NEW_EMAIL, newEmail); + } + catch (JSONException e) { + e.printStackTrace(); + } + + sendRequest(IterableConstants.ENDPOINT_UPDATEEMAIL, requestJSON); + + _email = newEmail; + } + + public void updateUser(JSONObject dataFields) { + JSONObject requestJSON = new JSONObject(); + + try { + requestJSON.put(IterableConstants.KEY_EMAIL, _email); + requestJSON.put(IterableConstants.KEY_DATAFIELDS, dataFields); + } + catch (JSONException e) { + e.printStackTrace(); + } + + sendRequest(IterableConstants.ENDPOINT_UPDATEUSER, requestJSON); + } + + public void disablePush(String iterableAppId, String gcmProjectId) { + registerForPush(iterableAppId, gcmProjectId); + + JSONObject requestJSON = new JSONObject(); + try { + requestJSON.put(IterableConstants.KEY_TOKEN, _pushToken); + requestJSON.put(IterableConstants.KEY_EMAIL, _email); + } + catch (JSONException e) { + e.printStackTrace(); + } + sendRequest(IterableConstants.ENDPOINT_DISABLEDEVICE, requestJSON); + + } + + /** + * Retrieves the payload string for a given key. + * Used for deeplinking and retrieving extra data passed down along with a campaign. + * @param key + * @return Returns the requested payload data from the current push campaign if it exists. + */ + public String getPayloadData(String key) { + String dataString = null; + if (_payloadData != null){ + dataString = _payloadData.getString(key, null); + } + return dataString; } - public static void semee(){ + void setPayloadData(Bundle bundle) { + _payloadData = bundle; + } + void setNotificationData(IterableNotificationData data) { + _notificationData = data; + } + /** + * Gets the current Template ID. + * @return returns 0 if the current templateId does not exist. + */ + public int getTemplateId() { + int returnId = 0; + if (_notificationData != null){ + returnId = _notificationData.getTemplateId(); + } + return returnId; } + /** + * Gets the current Campaign ID. + * @return 0 if the current templateId does not exist. + */ + public int getCampaignId() { + int returnId = 0; + if (_notificationData != null){ + returnId = _notificationData.getCampaignId(); + } + return returnId; + } + + public static void initDebugMode(String url) { + IterableRequest.overrideUrl = url; + } + + /** + * Sends the request to Iterable. + * Performs network operations on an async thread instead of the main thread. + * @param resourcePath + * @param json + */ + private void sendRequest(String resourcePath, JSONObject json) { + IterableApiRequest request = new IterableApiRequest(_apiKey, resourcePath, json.toString()); + new IterableRequest().execute(request); + } } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java new file mode 100644 index 000000000..a60ac368c --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java @@ -0,0 +1,52 @@ +package com.iterable.iterableapi; + +/** + * Created by David Truong dt@iterable.com + * + * IterableConstants contains a list of constants used with the Iterable mobile SDK. + */ +public final class IterableConstants { + public static final String ACTION_NOTIF_OPENED = "com.iterable.push.ACTION_NOTIF_OPENED"; + public static final String ACTION_PUSH_REGISTRATION = "com.iterable.push.ACTION_PUSH_REGISTRATION"; + + //API Endpoint Key Constants + public static final String KEY_API_KEY = "api_key"; + public static final String KEY_CAMPAIGNID = "campaignId"; + public static final String KEY_CURRENT_EMAIL = "currentEmail"; + public static final String KEY_DATAFIELDS = "dataFields"; + public static final String KEY_EMAIL = "email"; + public static final String KEY_EVENTNAME = "eventName"; + public static final String KEY_NEW_EMAIL = "newEmail"; + public static final String KEY_RECIPIENT_EMAIL = "recipientEmail"; + public static final String KEY_SEND_AT = "sendAt"; + public static final String KEY_TEMPLATE_ID = "templateId"; + public static final String KEY_TOKEN = "token"; + public static final String KEY_PLATFORM = "platform"; + public static final String KEY_APPLICATIONNAME = "applicationName"; + public static final String KEY_DEVICE = "device"; + public static final String KEY_USER = "user"; + public static final String KEY_ITEMS = "items"; + public static final String KEY_TOTAL = "total"; + + public static final String ENDPOINT_DISABLEDEVICE = "users/disableDevice"; + public static final String ENDPOINT_PUSHTARGET = "push/target"; + public static final String ENDPOINT_REGISTERDEVICETOKEN = "users/registerDeviceToken"; + public static final String ENDPOINT_TRACK = "events/track"; + public static final String ENDPOINT_TRACKCONVERSION = "events/trackConversion"; + public static final String ENDPOINT_TRACKPURCHASE = "commerce/trackPurchase"; + public static final String ENDPOINT_TRACKPUSHOPEN = "events/trackPushOpen"; + public static final String ENDPOINT_UPDATEEMAIL = "users/updateEmail"; + public static final String ENDPOINT_UPDATEUSER = "users/update"; + + + public static final String PUSH_APPID = "IterableAppId"; + public static final String PUSH_PROJECTID = "GCMProjectNumber"; + + public static final String MESSAGING_PLATFORM_GOOGLE = "GCM"; + public static final String MESSAGING_PLATFORM_AMAZON = "ADM"; + + public static final String IS_GHOST_PUSH = "isGhostPush"; + public static final String ITERABLE_DATA_KEY = "itbl"; + public static final String ITERABLE_DATA_BODY = "body"; + public static final String ITERABLE_DATA_TITLE = "title"; +} diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java new file mode 100644 index 000000000..829f67b6f --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableHelper.java @@ -0,0 +1,23 @@ +package com.iterable.iterableapi; + +import android.content.Intent; +import android.os.Bundle; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by David Truong dt@iterable.com + */ +public class IterableHelper { + public static boolean isGhostPush(Bundle extras) { + boolean isGhostPush = false; + if (extras.containsKey(IterableConstants.ITERABLE_DATA_KEY)) { + String iterableData = extras.getString(IterableConstants.ITERABLE_DATA_KEY); + IterableNotificationData data = new IterableNotificationData(iterableData); + isGhostPush = data.getIsGhostPush(); + } + + return isGhostPush; + } +} \ No newline at end of file diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableNotification.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableNotification.java new file mode 100644 index 000000000..6e02fd253 --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableNotification.java @@ -0,0 +1,81 @@ +package com.iterable.iterableapi; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.NotificationCompat; + +import java.util.Date; + +/** + * + * Created by David Truong dt@iterable.com + */ +public class IterableNotification extends NotificationCompat.Builder { + private boolean isGhostPush; + + protected IterableNotification(Context context) { + super(context); + } + + /** + * Creates and returns an instance of IterableNotification. + * @param context + * @param extras + * @param classToOpen + * @param icon + * @return Returns null if the intent comes from an Iterable ghostPush + */ + public static IterableNotification createNotification(Context context, Bundle extras, Class classToOpen, int icon) { + int stringId = context.getApplicationInfo().labelRes; + String applicationName = context.getString(stringId); + String notificationBody = null; + if (extras.containsKey(IterableConstants.ITERABLE_DATA_KEY)) { + notificationBody = extras.getString(IterableConstants.ITERABLE_DATA_BODY, notificationBody); + applicationName = extras.getString(IterableConstants.ITERABLE_DATA_TITLE, applicationName); + } + + Intent mainIntentWithExtras = new Intent(IterableConstants.ACTION_NOTIF_OPENED); + mainIntentWithExtras.setClass(context, classToOpen); + mainIntentWithExtras.putExtras(extras); + mainIntentWithExtras.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + PendingIntent notificationClickedIntent = PendingIntent.getActivity(context, 0, + mainIntentWithExtras, PendingIntent.FLAG_UPDATE_CURRENT); + + + + IterableNotification notificationBuilder = new IterableNotification(context); + notificationBuilder.setSmallIcon(icon) + .setContentTitle(applicationName) + .setContentText(notificationBody) + .setAutoCancel(true); + + notificationBuilder.setContentIntent(notificationClickedIntent); + + notificationBuilder.isGhostPush = IterableHelper.isGhostPush(extras); + + return notificationBuilder; + } + + /** + * Posts the notification on device. + * Only sets the notification if it is not a ghostPush/null iterableNotification. + * @param context + * @param iterableNotification Function assumes that the iterableNotification is a ghostPush + * if the IterableNotification passed in is null. + */ + public static void postNotificationOnDevice(Context context, IterableNotification iterableNotification) { + if ( !iterableNotification.isGhostPush) { + NotificationManager mNotificationManager = (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + + long dateInMilli = new Date().getTime(); + int notifID = (int) (dateInMilli % Integer.MAX_VALUE); + + mNotificationManager.notify(notifID, iterableNotification.build()); + } + } +} diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableNotificationData.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableNotificationData.java new file mode 100644 index 000000000..eb435b224 --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableNotificationData.java @@ -0,0 +1,53 @@ +package com.iterable.iterableapi; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by davidtruong on 5/23/16. + */ +class IterableNotificationData { + private int campaignId; + private int templateId; + private boolean isGhostPush; + + IterableNotificationData(String data){ + try { + JSONObject iterableJson = new JSONObject(data); + if (iterableJson.has(IterableConstants.KEY_CAMPAIGNID)){ + campaignId = iterableJson.getInt(IterableConstants.KEY_CAMPAIGNID); + } + + if (iterableJson.has(IterableConstants.KEY_TEMPLATE_ID)) { + templateId = iterableJson.getInt(IterableConstants.KEY_TEMPLATE_ID); + } + + if (iterableJson.has(IterableConstants.IS_GHOST_PUSH)) { + isGhostPush = iterableJson.getBoolean(IterableConstants.IS_GHOST_PUSH); + } + + //TODO: do we need to parse out any additional dataFields to pass to trackPushOpen? + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public int getCampaignId() + { + //include validation, logic, logging or whatever you like here + return this.campaignId; + } + + public int getTemplateId() + { + //include validation, logic, logging or whatever you like here + return this.templateId; + } + + public boolean getIsGhostPush() + { + //include validation, logic, logging or whatever you like here + return this.isGhostPush; + } +} + diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushOpenReceiver.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushOpenReceiver.java new file mode 100644 index 000000000..9d2402d6b --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushOpenReceiver.java @@ -0,0 +1,41 @@ +package com.iterable.iterableapi; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.CallSuper; + +/** + * Created by David Truong dt@iterable.com + * + * The IterablePushOpenReceiver should be used to handle broadcasts to track push opens. + * The sending intent should use the action: IterableConstants.ACTION_NOTIF_OPENED + * Additionally include the extra data passed down from GCM receive intent. + */ +public class IterablePushOpenReceiver extends BroadcastReceiver { + static final String TAG = "IterablePushOpenReceiver"; + + /** + * IterablePushOpenReceiver handles the broadcast for tracking a pushOpen. + * @param context + * @param intent + */ + @CallSuper + public void onReceive(Context context, Intent intent) { + String intentAction = intent.getAction(); + if (intentAction.equals(IterableConstants.ACTION_NOTIF_OPENED)){ + Bundle extras = intent.getExtras(); + if (extras != null && !extras.isEmpty() && extras.containsKey(IterableConstants.ITERABLE_DATA_KEY)) + { + String iterableDataString = extras.getString(IterableConstants.ITERABLE_DATA_KEY); + IterableNotificationData iterableNotificationData = new IterableNotificationData(iterableDataString); + if (IterableApi.sharedInstance != null) { + IterableApi.sharedInstance.setPayloadData(extras); + IterableApi.sharedInstance.setNotificationData(iterableNotificationData); + IterableApi.sharedInstance.trackPushOpen(iterableNotificationData.getCampaignId(), iterableNotificationData.getTemplateId()); + } + } + } + } +} diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushReceiver.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushReceiver.java new file mode 100644 index 000000000..538907dd9 --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushReceiver.java @@ -0,0 +1,74 @@ +package com.iterable.iterableapi; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.util.Log; + +/** + * + * Created by David Truong dt@iterable.com + */ +public class IterablePushReceiver extends BroadcastReceiver{ + + static final String TAG = "IterablePushReceiver"; + + private static final String ACTION_GCM_RECEIVE_INTENT = "com.google.android.c2dm.intent.RECEIVE"; + private static final String ACTION_GCM_REGISTRATION_INTENT = "com.google.android.c2dm.intent.REGISTRATION"; + + @Override + public void onReceive(Context context, Intent intent) { + String intentAction = intent.getAction(); + + if (intentAction.equals(IterableConstants.ACTION_PUSH_REGISTRATION)) { + handlePushRegistration(context, intent); + } else if (intentAction.equals(ACTION_GCM_RECEIVE_INTENT)) { + handlePushReceived(context, intent); + } else if (intentAction.equals(ACTION_GCM_REGISTRATION_INTENT)) { + Log.d(TAG, "received GCM registration intent action"); + } + } + + private void handlePushRegistration(Context context, Intent intent) { + String iterableAppId = intent.getStringExtra(IterableConstants.PUSH_APPID); + String projectNumber = intent.getStringExtra(IterableConstants.PUSH_PROJECTID); + IterableGCMRegistrationData data = new IterableGCMRegistrationData(iterableAppId, projectNumber); + new IterablePushRegistrationGCM().execute(data); + } + + private void handlePushReceived(Context context, Intent intent) { + Context appContext = context.getApplicationContext(); + + PackageManager packageManager = appContext.getPackageManager(); + Intent packageIntent = packageManager.getLaunchIntentForPackage(appContext.getPackageName()); + ComponentName componentPackageName = packageIntent.getComponent(); + String mainClassName = componentPackageName.getClassName(); + Class mainClass = null; + try { + mainClass = Class.forName(mainClassName); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + + int iconId = appContext.getResources().getIdentifier( + IterableApi.getNotificationIcon(context), + "drawable", + appContext.getPackageName()); + + if (iconId == 0) { + iconId = appContext.getApplicationInfo().icon; + if (iconId != 0){ + Log.d(TAG, "No Notification Icon defined - defaulting to app icon"); + } else { + Log.w(TAG, "No Notification Icon defined - push notifications will not be displayed"); + } + } + + IterableNotification notificationBuilder = IterableNotification.createNotification( + appContext, intent.getExtras(), mainClass, iconId); + + IterableNotification.postNotificationOnDevice(appContext, notificationBuilder); + } +} \ No newline at end of file diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushRegistrationGCM.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushRegistrationGCM.java new file mode 100644 index 000000000..e2a31cbcc --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushRegistrationGCM.java @@ -0,0 +1,60 @@ +package com.iterable.iterableapi; + +import android.os.AsyncTask; +import android.util.Log; + +import com.google.android.gms.gcm.GoogleCloudMessaging; +import com.google.android.gms.iid.InstanceID; + +import java.io.IOException; + +/** + * Created by David Truong dt@iterable.com + */ +class IterablePushRegistrationGCM extends AsyncTask { + static final String TAG = "IterableGCM"; + + protected String doInBackground(IterableGCMRegistrationData... params) { + try { + IterableGCMRegistrationData iterableGCMRegistrationData = params[0]; + + if (iterableGCMRegistrationData.iterableAppId != null) { + Class instanceIdClass = Class.forName("com.google.android.gms.iid.InstanceID"); + if (instanceIdClass != null) { + InstanceID instanceID = InstanceID.getInstance(IterableApi.sharedInstance.getMainActivityContext()); + String registrationToken = ""; + String idInstance = instanceID.getId(); + registrationToken = instanceID.getToken(iterableGCMRegistrationData.projectNumber, + GoogleCloudMessaging.INSTANCE_ID_SCOPE); + if (!registrationToken.isEmpty()) { + IterableApi.sharedInstance.setPushToken(registrationToken); + IterableApi.sharedInstance.registerDeviceToken(iterableGCMRegistrationData.iterableAppId, registrationToken); + } + } + } else { + Log.e("IterableGCM", "The IterableAppId has not been added to the AndroidManifest"); + } + } catch (ClassNotFoundException e) { + //Notes: If there is a ClassNotFoundException add + // compile 'com.google.android.gms:play-services-gcm:7.5.0' (min version) to the gradle dependencies + Log.e(TAG, "ClassNotFoundException: Check that play-services-gcm is added " + + "to the build dependencies"); + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + Log.e(TAG, "Invalid projectNumber"); + } + return null; + } +} + +class IterableGCMRegistrationData { + String iterableAppId = ""; + String projectNumber = ""; + public IterableGCMRegistrationData(String iterableAppId, String projectNumber){ + this.iterableAppId = iterableAppId; + this.projectNumber = projectNumber; + } +} + + diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequest.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequest.java new file mode 100644 index 000000000..2f559887e --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequest.java @@ -0,0 +1,124 @@ +package com.iterable.iterableapi; + +import android.os.AsyncTask; +import android.util.Log; + +import java.io.BufferedWriter; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * Async task to handle sending data to the Iterable server + * Created by David Truong dt@iterable.com + */ +class IterableRequest extends AsyncTask { + static final String TAG = "IterableRequest"; + static final String AUTHENTICATION_IO_EXCEPTION = "Received authentication challenge is null"; + static final int DEFAULT_TIMEOUT = 10000; + + long retryDelay = 10000; + + static final String iterableBaseUrl = "https://api.iterable.com/api/"; + + static String overrideUrl; + + /** + * Sends the given request to Iterable using a HttpUserConnection + * Reference - http://developer.android.com/reference/java/net/HttpURLConnection.html + * @param params + * @return + */ + protected String doInBackground(IterableApiRequest... params) { + IterableApiRequest iterableApiRequest = params[0]; + + String requestResult = null; + if (iterableApiRequest != null) { + URL url; + HttpURLConnection urlConnection = null; + + try { + String baseUrl = (overrideUrl != null && !overrideUrl.isEmpty()) ? overrideUrl : iterableBaseUrl; + url = new URL(baseUrl + iterableApiRequest.resourcePath); + + urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setDoOutput(true); + urlConnection.setRequestMethod("POST"); + + urlConnection.setReadTimeout(DEFAULT_TIMEOUT); + urlConnection.setConnectTimeout(DEFAULT_TIMEOUT); + + urlConnection.setRequestProperty("Accept", "application/json"); + urlConnection.setRequestProperty("Content-Type", "application/json"); + urlConnection.setRequestProperty(IterableConstants.KEY_API_KEY, iterableApiRequest.apiKey); + + OutputStream os = urlConnection.getOutputStream(); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8")); + writer.write(iterableApiRequest.json); + writer.close(); + os.close(); + + int responseCode = urlConnection.getResponseCode(); + if (responseCode >= 400) { + InputStream errorStream = urlConnection.getErrorStream(); + java.util.Scanner scanner = new java.util.Scanner(errorStream).useDelimiter("\\A"); + requestResult = scanner.hasNext() ? scanner.next() : ""; + Log.d(TAG, "Invalid Request for: " + iterableApiRequest.resourcePath); + Log.d(TAG, requestResult); + } + } catch (FileNotFoundException e) { + String mess = e.getMessage(); + e.printStackTrace(); + } catch (IOException e) { + String mess = e.getMessage(); + if (mess.equals(AUTHENTICATION_IO_EXCEPTION)) { + Log.d(TAG, "Invalid API Key"); + } else + { + retryRequest(iterableApiRequest); + } + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + retryRequest(iterableApiRequest); + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + } + } + return requestResult; + } + + @Override + protected void onPostExecute(String s) { + super.onPostExecute(s); + } + + private void retryRequest(IterableApiRequest iterableApiRequest) { + try { + wait(retryDelay); + } catch (InterruptedException e) { + e.printStackTrace(); + } + retryDelay *= 2; //exponential retry backoff + doInBackground(iterableApiRequest); + } + +} + +class IterableApiRequest { + String apiKey = ""; + String resourcePath = ""; + String json = ""; + + public IterableApiRequest(String apiKey, String resourcePath, String json){ + this.apiKey = apiKey; + this.resourcePath = resourcePath; + this.json = json; + } +} diff --git a/local.properties b/local.properties deleted file mode 100644 index 46f68fca6..000000000 --- a/local.properties +++ /dev/null @@ -1,10 +0,0 @@ -## This file is automatically generated by Android Studio. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file should *NOT* be checked into Version Control Systems, -# as it contains information specific to your local configuration. -# -# Location of the SDK. This is only used by Gradle. -# For customization when using a Version Control System, please read the -# header note. -sdk.dir=/Users/davidtruong/Library/Android/sdk \ No newline at end of file