Skip to content

Commit 0010a1b

Browse files
authored
Merge pull request #490 from Countly/preflight_checl
Preflight check
2 parents 4a9a6e6 + d781718 commit 0010a1b

File tree

7 files changed

+214
-46
lines changed

7 files changed

+214
-46
lines changed

sdk/src/androidTest/java/ly/count/android/sdk/ModuleConfigurationTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,10 @@ public void doWork(String requestData, String customEndpoint, ConnectionProcesso
10891089
}
10901090
};
10911091
}
1092+
1093+
@Override public ImmediateRequestI CreatePreflightRequestMaker() {
1094+
return null;
1095+
}
10921096
};
10931097
}
10941098

@@ -1162,6 +1166,10 @@ private int[] setupTest_allFeatures(JSONObject serverConfig) {
11621166
}
11631167
};
11641168
}
1169+
1170+
@Override public ImmediateRequestI CreatePreflightRequestMaker() {
1171+
return null;
1172+
}
11651173
};
11661174
countlyConfig.metricProviderOverride = new MockedMetricProvider();
11671175
Countly.sharedInstance().init(countlyConfig);

sdk/src/main/java/ly/count/android/sdk/ConnectionProcessor.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,63 @@ private enum RequestResult {
247247
return conn;
248248
}
249249

250+
synchronized public @NonNull URLConnection urlConnectionForPreflightRequest(@NonNull String preflightData) throws IOException {
251+
long approxSize = preflightData.length();
252+
URL url = new URL(preflightData);
253+
254+
long tOpen = pcc != null ? UtilsTime.getNanoTime() : 0;
255+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
256+
257+
if (conn instanceof HttpsURLConnection && (Countly.publicKeyPinCertificates != null || Countly.certificatePinCertificates != null)) {
258+
((HttpsURLConnection) conn).setSSLSocketFactory(sslContext_.getSocketFactory());
259+
}
260+
261+
if (pcc != null) {
262+
pcc.TrackCounterTimeNs("ConnectionProcessorUrlConnectionForPreflightRequest_01_OpenURLConnection", UtilsTime.getNanoTime() - tOpen);
263+
tOpen = UtilsTime.getNanoTime();
264+
}
265+
266+
conn.setRequestMethod("HEAD");
267+
conn.setConnectTimeout(CONNECT_TIMEOUT_IN_MILLISECONDS);
268+
conn.setReadTimeout(READ_TIMEOUT_IN_MILLISECONDS);
269+
conn.setUseCaches(false);
270+
conn.setDoInput(true);
271+
conn.setDoOutput(false);
272+
conn.setInstanceFollowRedirects(true);
273+
274+
if (requestHeaderCustomValues_ != null) {
275+
L.v("[ConnectionProcessor] Adding [" + requestHeaderCustomValues_.size() + "] custom header fields");
276+
for (Map.Entry<String, String> e : requestHeaderCustomValues_.entrySet()) {
277+
if (e.getKey() != null && e.getValue() != null && !e.getKey().isEmpty()) {
278+
conn.addRequestProperty(e.getKey(), e.getValue());
279+
}
280+
}
281+
}
282+
283+
if (pcc != null) {
284+
pcc.TrackCounterTimeNs("ConnectionProcessorUrlConnectionForPreflightRequest_02_ConfigureConnection", UtilsTime.getNanoTime() - tOpen);
285+
tOpen = UtilsTime.getNanoTime();
286+
}
287+
288+
try {
289+
for (int i = 0; ; i++) {
290+
String key = conn.getHeaderFieldKey(i);
291+
if (key == null) break;
292+
String value = conn.getHeaderField(i);
293+
approxSize += key.getBytes(StandardCharsets.US_ASCII).length + value.getBytes(StandardCharsets.US_ASCII).length + 2L;
294+
}
295+
} catch (Exception e) {
296+
L.e("[ConnectionProcessor] urlConnectionForPreflightRequest, exception while calculating header field size: " + e);
297+
}
298+
299+
if (pcc != null) {
300+
pcc.TrackCounterTimeNs("ConnectionProcessorUrlConnectionForPreflightRequest_03_HeaderFieldSize", UtilsTime.getNanoTime() - tOpen);
301+
}
302+
303+
L.v("[ConnectionProcessor] urlConnectionForPreflightRequest, Approx data size: [" + approxSize + " B]");
304+
return conn;
305+
}
306+
250307
/**
251308
* Return the size of the text multipart entry
252309
*

sdk/src/main/java/ly/count/android/sdk/Countly.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,10 @@ public synchronized Countly init(CountlyConfig config) {
493493
@Override public ImmediateRequestI CreateImmediateRequestMaker() {
494494
return (new ImmediateRequestMaker());
495495
}
496+
497+
@Override public ImmediateRequestI CreatePreflightRequestMaker() {
498+
return (new PreflightRequestMaker());
499+
}
496500
};
497501
}
498502

sdk/src/main/java/ly/count/android/sdk/ImmediateRequestGenerator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22

33
interface ImmediateRequestGenerator {
44
ImmediateRequestI CreateImmediateRequestMaker();
5+
6+
ImmediateRequestI CreatePreflightRequestMaker();
57
}

sdk/src/main/java/ly/count/android/sdk/ModuleContent.java

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -76,28 +76,37 @@ void fetchContentsInternal(@NonNull String[] categories) {
7676
try {
7777
if (validateResponse(checkResponse)) {
7878
L.d("[ModuleContent] fetchContentsInternal, got new content data, showing it");
79-
Map<Integer, TransparentActivityConfig> placementCoordinates = parseContent(checkResponse, displayMetrics);
80-
if (placementCoordinates.isEmpty()) {
81-
L.d("[ModuleContent] fetchContentsInternal, placement coordinates are empty, skipping");
82-
return;
83-
}
84-
85-
Intent intent = new Intent(_cly.context_, TransparentActivity.class);
86-
intent.putExtra(TransparentActivity.CONFIGURATION_LANDSCAPE, placementCoordinates.get(Configuration.ORIENTATION_LANDSCAPE));
87-
intent.putExtra(TransparentActivity.CONFIGURATION_PORTRAIT, placementCoordinates.get(Configuration.ORIENTATION_PORTRAIT));
88-
intent.putExtra(TransparentActivity.ORIENTATION, _cly.context_.getResources().getConfiguration().orientation);
89-
90-
Long id = System.currentTimeMillis();
91-
intent.putExtra(TransparentActivity.ID_CALLBACK, id);
92-
if (globalContentCallback != null) {
93-
TransparentActivity.contentCallbacks.put(id, globalContentCallback);
94-
}
95-
96-
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
97-
_cly.context_.startActivity(intent);
98-
99-
shouldFetchContents = false; // disable fetching contents until the next time, this will disable the timer fetching
100-
isCurrentlyInContentZone = true;
79+
80+
iRGenerator.CreatePreflightRequestMaker().doWork(checkResponse.optString("html"), null, cp, false, true, preflightResponse -> {
81+
if (preflightResponse == null) {
82+
L.d("[ModuleContent] fetchContentsInternal, preflight check failed, skipping showing content");
83+
return;
84+
}
85+
86+
Map<Integer, TransparentActivityConfig> placementCoordinates = parseContent(checkResponse, displayMetrics);
87+
if (placementCoordinates.isEmpty()) {
88+
L.d("[ModuleContent] fetchContentsInternal, placement coordinates are empty, skipping");
89+
return;
90+
}
91+
92+
L.d("[ModuleContent] fetchContentsInternal, preflight check succeeded");
93+
Intent intent = new Intent(_cly.context_, TransparentActivity.class);
94+
intent.putExtra(TransparentActivity.CONFIGURATION_LANDSCAPE, placementCoordinates.get(Configuration.ORIENTATION_LANDSCAPE));
95+
intent.putExtra(TransparentActivity.CONFIGURATION_PORTRAIT, placementCoordinates.get(Configuration.ORIENTATION_PORTRAIT));
96+
intent.putExtra(TransparentActivity.ORIENTATION, _cly.context_.getResources().getConfiguration().orientation);
97+
98+
Long id = System.currentTimeMillis();
99+
intent.putExtra(TransparentActivity.ID_CALLBACK, id);
100+
if (globalContentCallback != null) {
101+
TransparentActivity.contentCallbacks.put(id, globalContentCallback);
102+
}
103+
104+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
105+
_cly.context_.startActivity(intent);
106+
107+
shouldFetchContents = false; // disable fetching contents until the next time, this will disable the timer fetching
108+
isCurrentlyInContentZone = true;
109+
}, L);
101110
} else {
102111
L.w("[ModuleContent] fetchContentsInternal, response is not valid, skipping");
103112
}

sdk/src/main/java/ly/count/android/sdk/ModuleFeedback.java

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -268,35 +268,45 @@ void presentFeedbackWidgetInternal(@Nullable final CountlyFeedbackWidget widgetI
268268
widgetListUrl.append(customObjectToSendWithTheWidget);
269269

270270
String preparedWidgetUrl = widgetListUrl.toString();
271-
272271
L.d("[ModuleFeedback] Using following url for widget:[" + preparedWidgetUrl + "]");
273-
if (!Utils.isNullOrEmpty(widgetInfo.widgetVersion)) {
274-
L.d("[ModuleFeedback] Will use transparent activity for displaying the widget");
275-
showFeedbackWidget_newActivity(context, preparedWidgetUrl, widgetInfo, devCallback);
276-
} else {
277-
L.d("[ModuleFeedback] Will use dialog for displaying the widget");
278-
//enable for chrome debugging
279-
// WebView.setWebContentsDebuggingEnabled(true);
280-
Handler handler = new Handler(Looper.getMainLooper());
281-
handler.post(new Runnable() {
282-
public void run() {
283-
L.d("[ModuleFeedback] Calling on main thread");
284272

285-
try {
286-
showFeedbackWidget(context, widgetInfo, closeButtonText, devCallback, preparedWidgetUrl);
273+
iRGenerator.CreatePreflightRequestMaker().doWork(preparedWidgetUrl, null, requestQueueProvider.createConnectionProcessor(), false, true, preflightResponse -> {
274+
if (preflightResponse == null) {
275+
L.e("[ModuleFeedback] Failed to do preflight check for the widget url");
276+
if (devCallback != null) {
277+
devCallback.onFinished("Failed to do preflight check for the widget url");
278+
}
279+
return;
280+
}
287281

288-
if (devCallback != null) {
289-
devCallback.onFinished(null);
290-
}
291-
} catch (Exception ex) {
292-
L.e("[ModuleFeedback] Failed at displaying feedback widget dialog, [" + ex.toString() + "]");
293-
if (devCallback != null) {
294-
devCallback.onFinished("Failed at displaying feedback widget dialog, [" + ex.toString() + "]");
282+
if (!Utils.isNullOrEmpty(widgetInfo.widgetVersion)) {
283+
L.d("[ModuleFeedback] Will use transparent activity for displaying the widget");
284+
showFeedbackWidget_newActivity(context, preparedWidgetUrl, widgetInfo, devCallback);
285+
} else {
286+
L.d("[ModuleFeedback] Will use dialog for displaying the widget");
287+
//enable for chrome debugging
288+
// WebView.setWebContentsDebuggingEnabled(true);
289+
Handler handler = new Handler(Looper.getMainLooper());
290+
handler.post(new Runnable() {
291+
public void run() {
292+
L.d("[ModuleFeedback] Calling on main thread");
293+
294+
try {
295+
showFeedbackWidget(context, widgetInfo, closeButtonText, devCallback, preparedWidgetUrl);
296+
297+
if (devCallback != null) {
298+
devCallback.onFinished(null);
299+
}
300+
} catch (Exception ex) {
301+
L.e("[ModuleFeedback] Failed at displaying feedback widget dialog, [" + ex.toString() + "]");
302+
if (devCallback != null) {
303+
devCallback.onFinished("Failed at displaying feedback widget dialog, [" + ex.toString() + "]");
304+
}
295305
}
296306
}
297-
}
298-
});
299-
}
307+
});
308+
}
309+
}, L);
300310
}
301311

302312
private void showFeedbackWidget(Context context, CountlyFeedbackWidget widgetInfo, String closeButtonText, FeedbackCallback devCallback, String url) {
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package ly.count.android.sdk;
2+
3+
import android.os.AsyncTask;
4+
import androidx.annotation.NonNull;
5+
import androidx.annotation.Nullable;
6+
import java.io.IOException;
7+
import java.net.HttpURLConnection;
8+
import org.json.JSONObject;
9+
10+
class PreflightRequestMaker extends AsyncTask<Object, Void, Boolean> implements ImmediateRequestI {
11+
12+
ImmediateRequestMaker.InternalImmediateRequestCallback callback;
13+
ModuleLog L;
14+
15+
@Override
16+
public void doWork(@NonNull String requestData, @Nullable String customEndpoint, @NonNull ConnectionProcessor cp, boolean requestShouldBeDelayed, boolean networkingIsEnabled, @NonNull ImmediateRequestMaker.InternalImmediateRequestCallback callback, @NonNull ModuleLog log) {
17+
assert Utils.isNotNullOrEmpty(requestData);
18+
assert cp != null;
19+
assert log != null;
20+
assert callback != null;
21+
22+
this.execute(requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log);
23+
}
24+
25+
/**
26+
* params fields:
27+
* 0 - requestData
28+
* 1 - custom endpoint
29+
* 2 - connection processor
30+
* 3 - requestShouldBeDelayed
31+
* 4 - networkingIsEnabled
32+
* 5 - callback
33+
* 6 - log module
34+
*/
35+
protected Boolean doInBackground(Object... params) {
36+
final String urlRequest = (String) params[0];
37+
final ConnectionProcessor cp = (ConnectionProcessor) params[2];
38+
callback = (ImmediateRequestMaker.InternalImmediateRequestCallback) params[5];
39+
L = (ModuleLog) params[6];
40+
41+
L.v("[PreflightRequestMaker] doPreflightRequest, Starting preflight request");
42+
43+
HttpURLConnection connection = null;
44+
45+
try {
46+
//getting connection ready
47+
try {
48+
connection = (HttpURLConnection) cp.urlConnectionForPreflightRequest(urlRequest);
49+
} catch (IOException e) {
50+
L.e("[PreflightRequestMaker] doPreflightRequest, IOException while preparing preflight request :[" + e + "]");
51+
return null;
52+
}
53+
54+
int responseCode = connection.getResponseCode();
55+
56+
L.v("[PreflightRequestMaker] doPreflightRequest, Preflight request finished, response code: " + responseCode);
57+
return responseCode >= 200 && responseCode < 400;
58+
} catch (Exception e) {
59+
L.e("[PreflightRequestMaker] doPreflightRequest, Received exception while making a immediate server request", e);
60+
} finally {
61+
if (connection != null) {
62+
connection.disconnect();
63+
}
64+
}
65+
L.v("[PreflightRequestMaker] doPreflightRequest, Finished request");
66+
return false;
67+
}
68+
69+
@Override
70+
protected void onPostExecute(Boolean result) {
71+
super.onPostExecute(result);
72+
L.v("[PreflightRequestMaker] onPostExecute");
73+
74+
if (callback != null) {
75+
callback.callback(result ? new JSONObject() : null);
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)