diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a27324ce..d1b792cc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ * `SAFE_AREA` mode: Omits status bar, navigation bar and cutouts when displaying webviews. * Added a new init config option `disableGradualRequestCleaner()` to change request queue overflow behavior. When enabled, all overflowing requests (plus one slot) are removed at once instead of being cleaned gradually in limited batches. +* Added a new method `requestQueue().addCustomNetworkRequestHeaders(Map)` for providing or overriding custom headers after init + * Immediate requests now will be run by parallel executor instead of serial by default. ## 25.4.4 diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/CustomHeaderRuntimeTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/CustomHeaderRuntimeTests.java new file mode 100644 index 000000000..7ff64cc6e --- /dev/null +++ b/sdk/src/androidTest/java/ly/count/android/sdk/CustomHeaderRuntimeTests.java @@ -0,0 +1,63 @@ +package ly.count.android.sdk; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.Mockito.mock; + +/** + * Tests for runtime custom header manipulation via Countly.addCustomNetworkRequestHeaders(Map) + */ +@RunWith(AndroidJUnit4.class) +public class CustomHeaderRuntimeTests { + + Countly mCountly; + + @Before + public void setUp() { + final CountlyStore countlyStore = new CountlyStore(TestUtils.getContext(), mock(ModuleLog.class)); + countlyStore.clear(); + + mCountly = new Countly(); + mCountly.init(new CountlyConfig(TestUtils.getContext(), "appkey", "http://test.count.ly").setDeviceId("1234").setLoggingEnabled(true)); + } + + @Test + public void testRuntimeAddAndOverrideHeaders() throws Exception { + // Add initial headers at runtime + Map initial = new HashMap<>(); + initial.put("X-First", "One"); + initial.put("X-Second", "Two"); + mCountly.requestQueue().addCustomNetworkRequestHeaders(initial); + + // Override one and add a new one + Map second = new HashMap<>(); + second.put("X-Second", "TwoOverride"); + second.put("X-Third", "Three"); + mCountly.requestQueue().addCustomNetworkRequestHeaders(second); + + ConnectionProcessor cp = new ConnectionProcessor( + "http://test.count.ly", + mCountly.countlyStore, + mCountly.config_.deviceIdProvider, + mCountly.config_.configProvider, + mCountly.connectionQueue_.requestInfoProvider, + null, + mCountly.requestHeaderCustomValues, + mCountly.L, + mCountly.config_.healthTracker, + mock(Runnable.class) + ); + + URLConnection urlConnection = cp.urlConnectionForServerRequest("a=b", null); + + Assert.assertEquals("One", urlConnection.getRequestProperty("X-First")); + Assert.assertEquals("TwoOverride", urlConnection.getRequestProperty("X-Second")); + Assert.assertEquals("Three", urlConnection.getRequestProperty("X-Third")); + } +} diff --git a/sdk/src/main/java/ly/count/android/sdk/Countly.java b/sdk/src/main/java/ly/count/android/sdk/Countly.java index c89a32dc0..de8e42595 100644 --- a/sdk/src/main/java/ly/count/android/sdk/Countly.java +++ b/sdk/src/main/java/ly/count/android/sdk/Countly.java @@ -1157,6 +1157,49 @@ public void setLoggingEnabled(final boolean enableLogging) { L.d("Enabling logging"); } + /** + * To add new header key/value pairs or override existing ones. + * A null or empty map is ignored. Null or empty keys, as well as null values, are ignored. + * Subsequent requests (including those created after overriding) will contain the updated header set. + * + * @param customHeaderValues map of header key/value pairs to add/override + * @return Returns the same Countly instance for convenient chaining + */ + /* package */ synchronized void addCustomNetworkRequestHeaders(Map customHeaderValues) { + if (!isInitialized()) { + L.e("[addCustomNetworkRequestHeaders] SDK must be initialised before calling this method"); + return; + } + + if (customHeaderValues == null || customHeaderValues.isEmpty()) { + L.d("[addCustomNetworkRequestHeaders] Provided map was null or empty, ignoring"); + return; + } + + if (requestHeaderCustomValues == null) { + requestHeaderCustomValues = new HashMap<>(); + } + + int added = 0; + int overridden = 0; + for (Map.Entry entry : customHeaderValues.entrySet()) { + String k = entry.getKey(); + String v = entry.getValue(); + if (k == null || k.isEmpty() || v == null) { + continue; // skip invalid entries + } + if (requestHeaderCustomValues.containsKey(k)) { + overridden++; + } else { + added++; + } + requestHeaderCustomValues.put(k, v); + } + + connectionQueue_.setRequestHeaderCustomValues(requestHeaderCustomValues); + L.i("[addCustomNetworkRequestHeaders] Added:" + added + " Overridden:" + overridden + " TotalNow:" + requestHeaderCustomValues.size()); + } + /** * Check if logging has been enabled internally in the SDK * diff --git a/sdk/src/main/java/ly/count/android/sdk/ModuleRequestQueue.java b/sdk/src/main/java/ly/count/android/sdk/ModuleRequestQueue.java index abd1bb4c9..d446bee4e 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleRequestQueue.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleRequestQueue.java @@ -456,5 +456,21 @@ public void recordMetrics(@Nullable Map metricsOverride) { recordMetricsInternal(tempMetricsOverride); } } + + /** + * To add new header key/value pairs or override existing ones. + * A null or empty map is ignored. Null or empty keys, as well as null values, + * are ignored. + * Subsequent requests (including those created after overriding) will contain + * the updated header set. + * + * @param customHeaderValues header key/value pairs to add or override + */ + public void addCustomNetworkRequestHeaders(@Nullable Map customHeaderValues) { + synchronized (_cly) { + L.i("[RequestQueue] addCustomNetworkRequestHeaders, Calling addCustomNetworkRequestHeaders"); + _cly.addCustomNetworkRequestHeaders(customHeaderValues); + } + } } }