Skip to content

Commit c6a218a

Browse files
authored
Merge pull request #62 from Iterable/feature/ITBL-6259-deferred-deep-linking
[ITBL-6259] Deferred deep linking support
2 parents 8264005 + 7528f69 commit c6a218a

File tree

9 files changed

+335
-5
lines changed

9 files changed

+335
-5
lines changed

iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java

+59-4
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
import android.content.Intent;
77
import android.content.SharedPreferences;
88
import android.graphics.Rect;
9+
import android.net.Uri;
910
import android.os.Build;
1011
import android.os.Bundle;
1112

1213
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
1314
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
1415
import com.google.android.gms.common.GooglePlayServicesRepairableException;
15-
import com.google.firebase.iid.FirebaseInstanceId;
16+
import com.iterable.iterableapi.ddl.DeviceInfo;
17+
import com.iterable.iterableapi.ddl.MatchFpResponse;
1618

1719
import org.json.JSONArray;
1820
import org.json.JSONException;
@@ -23,11 +25,8 @@
2325
import java.util.Arrays;
2426
import java.util.Date;
2527
import java.util.List;
26-
import java.util.Map;
2728
import java.util.TimeZone;
2829
import java.util.UUID;
29-
import java.util.regex.Matcher;
30-
import java.util.regex.Pattern;
3130

3231
/**
3332
* Created by David Truong [email protected]
@@ -226,6 +225,8 @@ public static void initialize(Context context, String apiKey, IterableConfig con
226225
}
227226
sharedInstance.sdkCompatEnabled = false;
228227
sharedInstance.retrieveEmailAndUserId();
228+
sharedInstance.checkForDeferredDeeplink();
229+
229230
if (sharedInstance.config.autoPushRegistration && sharedInstance.isInitialized()) {
230231
sharedInstance.registerForPush();
231232
}
@@ -1323,6 +1324,60 @@ private void onLogIn() {
13231324
}
13241325
}
13251326

1327+
private boolean getDDLChecked() {
1328+
return getPreferences().getBoolean(IterableConstants.SHARED_PREFS_DDL_CHECKED_KEY, false);
1329+
}
1330+
1331+
private void setDDLChecked(boolean value) {
1332+
getPreferences().edit().putBoolean(IterableConstants.SHARED_PREFS_DDL_CHECKED_KEY, value).apply();
1333+
}
1334+
1335+
private void checkForDeferredDeeplink() {
1336+
if (!config.checkForDeferredDeeplink) {
1337+
return;
1338+
}
1339+
1340+
try {
1341+
if (getDDLChecked()) {
1342+
return;
1343+
}
1344+
1345+
JSONObject requestJSON = DeviceInfo.createDeviceInfo(_applicationContext).toJSONObject();
1346+
1347+
IterableApiRequest request = new IterableApiRequest(_apiKey, IterableConstants.BASE_URL_LINKS,
1348+
IterableConstants.ENDPOINT_DDL_MATCH, requestJSON, IterableApiRequest.POST, new IterableHelper.SuccessHandler() {
1349+
@Override
1350+
public void onSuccess(JSONObject data) {
1351+
handleDDL(data);
1352+
}
1353+
}, new IterableHelper.FailureHandler() {
1354+
@Override
1355+
public void onFailure(String reason, JSONObject data) {
1356+
IterableLogger.e(TAG, "Error while checking deferred deep link: " + reason + ", response: " + data);
1357+
}
1358+
});
1359+
new IterableRequest().execute(request);
1360+
1361+
} catch (Exception e) {
1362+
IterableLogger.e(TAG, "Error while checking deferred deep link", e);
1363+
}
1364+
}
1365+
1366+
private void handleDDL(JSONObject response) {
1367+
IterableLogger.d(TAG, "handleDDL: " + response);
1368+
try {
1369+
MatchFpResponse matchFpResponse = MatchFpResponse.fromJSONObject(response);
1370+
1371+
if (matchFpResponse.isMatch) {
1372+
IterableAction action = IterableAction.actionOpenUrl(matchFpResponse.destinationUrl);
1373+
IterableActionRunner.executeAction(getMainActivityContext(), action, IterableActionSource.APP_LINK);
1374+
}
1375+
} catch (JSONException e) {
1376+
IterableLogger.e(TAG, "Error while handling deferred deep link", e);
1377+
}
1378+
setDDLChecked(true);
1379+
}
1380+
13261381
//---------------------------------------------------------------------------------------
13271382
//endregion
13281383

iterableapi/src/main/java/com/iterable/iterableapi/IterableConfig.java

+18
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,19 @@ public class IterableConfig {
3434
*/
3535
final String legacyGCMSenderId;
3636

37+
/**
38+
* When set to true, it will check for deferred deep links on first time app launch
39+
* after installation.
40+
*/
41+
final boolean checkForDeferredDeeplink;
42+
3743
private IterableConfig(Builder builder) {
3844
pushIntegrationName = builder.pushIntegrationName;
3945
urlHandler = builder.urlHandler;
4046
customActionHandler = builder.customActionHandler;
4147
autoPushRegistration = builder.autoPushRegistration;
4248
legacyGCMSenderId = builder.legacyGCMSenderId;
49+
checkForDeferredDeeplink = builder.checkForDeferredDeeplink;
4350
}
4451

4552
public static class Builder {
@@ -48,6 +55,7 @@ public static class Builder {
4855
private IterableCustomActionHandler customActionHandler;
4956
private boolean autoPushRegistration = true;
5057
private String legacyGCMSenderId;
58+
private boolean checkForDeferredDeeplink;
5159

5260
public Builder() {}
5361

@@ -101,6 +109,16 @@ public Builder setLegacyGCMSenderId(String legacyGCMSenderId) {
101109
return this;
102110
}
103111

112+
/**
113+
* When set to true, it will check for deferred deep links on first time app launch
114+
* after installation.
115+
* @param checkForDeferredDeeplink Enable deferred deep link checks on first launch
116+
*/
117+
public Builder setCheckForDeferredDeeplink(boolean checkForDeferredDeeplink) {
118+
this.checkForDeferredDeeplink = checkForDeferredDeeplink;
119+
return this;
120+
}
121+
104122
public IterableConfig build() {
105123
return new IterableConfig(this);
106124
}

iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ public final class IterableConstants {
1010
public static final String ACTION_PUSH_ACTION = "com.iterable.push.ACTION_PUSH_ACTION";
1111
public static final String ACTION_PUSH_REGISTRATION = "com.iterable.push.ACTION_PUSH_REGISTRATION";
1212

13+
//Hosts
14+
public static final String BASE_URL_API = "https://api.iterable.com/api/";
15+
public static final String BASE_URL_LINKS = "https://links.iterable.com/";
16+
1317
//API Fields
1418
public static final String KEY_API_KEY = "api_key";
1519
public static final String KEY_APPLICATION_NAME = "applicationName";
@@ -49,6 +53,7 @@ public final class IterableConstants {
4953
public static final String ENDPOINT_UPDATE_USER = "users/update";
5054
public static final String ENDPOINT_UPDATE_EMAIL = "users/updateEmail";
5155
public static final String ENDPOINT_UPDATE_USER_SUBS = "users/updateSubscriptions";
56+
public static final String ENDPOINT_DDL_MATCH = "a/matchFp";
5257

5358
public static final String PUSH_APP_ID = "IterableAppId";
5459
public static final String PUSH_GCM_PROJECT_NUMBER = "GCMProjectNumber";
@@ -77,6 +82,7 @@ public final class IterableConstants {
7782
public static final String SHARED_PREFS_EMAIL_KEY = "itbl_email";
7883
public static final String SHARED_PREFS_USERID_KEY = "itbl_userid";
7984
public static final String SHARED_PREFS_DEVICEID_KEY = "itbl_deviceid";
85+
public static final String SHARED_PREFS_DDL_CHECKED_KEY = "itbl_ddl_checked";
8086
public static final String SHARED_PREFS_EXPIRATION_SUFFIX = "_expiration";
8187
public static final String SHARED_PREFS_OBJECT_SUFFIX = "_object";
8288
public static final String SHARED_PREFS_PAYLOAD_KEY = "itbl_payload";

iterableapi/src/main/java/com/iterable/iterableapi/IterableRequest.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ protected String doInBackground(IterableApiRequest... params) {
5858
HttpURLConnection urlConnection = null;
5959

6060
try {
61-
String baseUrl = (overrideUrl != null && !overrideUrl.isEmpty()) ? overrideUrl : iterableBaseUrl;
61+
String baseUrl = (iterableApiRequest.baseUrl != null && !iterableApiRequest.baseUrl.isEmpty()) ? iterableApiRequest.baseUrl : iterableBaseUrl;
62+
if (overrideUrl != null && !overrideUrl.isEmpty()) {
63+
baseUrl = overrideUrl;
64+
}
65+
6266
if (iterableApiRequest.requestType == IterableApiRequest.GET) {
6367
Uri.Builder builder = Uri.parse(baseUrl+iterableApiRequest.resourcePath).buildUpon();
6468
builder.appendQueryParameter(IterableConstants.KEY_API_KEY, iterableApiRequest.apiKey);
@@ -234,6 +238,7 @@ class IterableApiRequest {
234238
static String POST = "POST";
235239

236240
String apiKey = "";
241+
String baseUrl = null;
237242
String resourcePath = "";
238243
JSONObject json;
239244
String requestType = "";
@@ -242,6 +247,16 @@ class IterableApiRequest {
242247
IterableHelper.SuccessHandler successCallback;
243248
IterableHelper.FailureHandler failureCallback;
244249

250+
public IterableApiRequest(String apiKey, String baseUrl, String resourcePath, JSONObject json, String requestType, IterableHelper.SuccessHandler onSuccess, IterableHelper.FailureHandler onFailure) {
251+
this.apiKey = apiKey;
252+
this.baseUrl = baseUrl;
253+
this.resourcePath = resourcePath;
254+
this.json = json;
255+
this.requestType = requestType;
256+
this.successCallback = onSuccess;
257+
this.failureCallback = onFailure;
258+
}
259+
245260
public IterableApiRequest(String apiKey, String resourcePath, JSONObject json, String requestType, IterableHelper.SuccessHandler onSuccess, IterableHelper.FailureHandler onFailure) {
246261
this.apiKey = apiKey;
247262
this.resourcePath = resourcePath;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.iterable.iterableapi.ddl;
2+
3+
import android.content.Context;
4+
import android.os.Build;
5+
import android.util.DisplayMetrics;
6+
import android.view.Display;
7+
import android.view.WindowManager;
8+
9+
import org.json.JSONException;
10+
import org.json.JSONObject;
11+
12+
import java.util.Date;
13+
import java.util.Locale;
14+
import java.util.TimeZone;
15+
16+
public class DeviceInfo {
17+
String mobileDeviceType = "Android";
18+
DeviceFp deviceFp;
19+
20+
private DeviceInfo(DeviceFp deviceFp) {
21+
this.deviceFp = deviceFp;
22+
}
23+
24+
static class DeviceFp {
25+
String screenWidth;
26+
String screenHeight;
27+
String screenScale;
28+
String version;
29+
String timezoneOffsetMinutes;
30+
String language;
31+
32+
JSONObject toJSONObject() throws JSONException {
33+
JSONObject json = new JSONObject();
34+
json.putOpt("screenWidth", screenWidth);
35+
json.putOpt("screenHeight", screenHeight);
36+
json.putOpt("screenScale", screenScale);
37+
json.putOpt("version", version);
38+
json.putOpt("timezoneOffsetMinutes", timezoneOffsetMinutes);
39+
json.putOpt("language", language);
40+
return json;
41+
}
42+
}
43+
44+
public static DeviceInfo createDeviceInfo(Context context) {
45+
return new DeviceInfo(createDeviceFp(context));
46+
}
47+
48+
private static DeviceFp createDeviceFp(Context context) {
49+
DeviceFp fp = new DeviceFp();
50+
51+
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
52+
Display display = wm.getDefaultDisplay();
53+
DisplayMetrics displayMetrics = new DisplayMetrics();
54+
if (Build.VERSION.SDK_INT >= 17) {
55+
display.getRealMetrics(displayMetrics);
56+
} else {
57+
display.getMetrics(displayMetrics);
58+
}
59+
fp.screenWidth = Long.toString(Math.round(Math.ceil(displayMetrics.widthPixels / displayMetrics.density)));
60+
fp.screenHeight = Long.toString(Math.round(Math.ceil(displayMetrics.heightPixels / displayMetrics.density)));
61+
fp.screenScale = Float.toString(displayMetrics.density);
62+
63+
fp.version = Build.VERSION.RELEASE;
64+
65+
// We're comparing with Javascript timezone offset, which is the difference between the
66+
// local time and UTC, so we need to flip the sign
67+
TimeZone timezone = TimeZone.getDefault();
68+
int seconds = -1 * timezone.getOffset(new Date().getTime()) / 1000;
69+
int offsetMinutes = seconds / 60;
70+
fp.timezoneOffsetMinutes = Integer.toString(offsetMinutes);
71+
72+
String countryCode = Locale.getDefault().getCountry();
73+
String languageCode = Locale.getDefault().getLanguage();
74+
fp.language = languageCode + "_" + countryCode;
75+
76+
return fp;
77+
}
78+
79+
public JSONObject toJSONObject() throws JSONException {
80+
JSONObject json = new JSONObject();
81+
json.put("mobileDeviceType", mobileDeviceType);
82+
json.put("deviceFp", deviceFp.toJSONObject());
83+
return json;
84+
}
85+
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.iterable.iterableapi.ddl;
2+
3+
import org.json.JSONException;
4+
import org.json.JSONObject;
5+
6+
public class MatchFpResponse {
7+
public final boolean isMatch;
8+
public final String destinationUrl;
9+
public final int campaignId;
10+
public final int templateId;
11+
public final String messageId;
12+
13+
public MatchFpResponse(boolean isMatch, String destinationUrl, int campaignId, int templateId, String messageId) {
14+
this.isMatch = isMatch;
15+
this.destinationUrl = destinationUrl;
16+
this.campaignId = campaignId;
17+
this.templateId = templateId;
18+
this.messageId = messageId;
19+
}
20+
21+
public static MatchFpResponse fromJSONObject(JSONObject json) throws JSONException {
22+
return new MatchFpResponse(
23+
json.getBoolean("isMatch"),
24+
json.getString("destinationUrl"),
25+
json.getInt("campaignId"),
26+
json.getInt("templateId"),
27+
json.getString("messageId")
28+
);
29+
}
30+
}

0 commit comments

Comments
 (0)