Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 3.3.0 #519

Merged
merged 10 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/instrumented.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
uses: reactivecircus/[email protected]
with:
api-level: ${{ matrix.api-level }}
avd-name: macOS-avd-arm64-v8a-29
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
Expand Down
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
3.3.0 (Jul 18, 2023)
- Improved streaming architecture implementation to apply feature flag updates from the notification received which is now enhanced, improving efficiency and reliability of the whole update system.
- Added logic to do a full check of feature flags immediately when the app comes back to foreground, limited to once per minute.

3.2.2 (Jun 7, 2023)
- Refactored cipher creation to avoid NPE scenarios.

Expand Down
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ apply plugin: 'signing'
apply plugin: 'kotlin-android'

ext {
splitVersion = '3.2.2'
splitVersion = '3.3.0'
}

android {
Expand Down Expand Up @@ -98,8 +98,8 @@ dependencies {
def lifecycleVersion = '2.5.1'
def annotationVersion = '1.2.0'
def gsonVersion = '2.9.1'
def guavaVersion = '31.1-android'
def snakeYamlVersion = '1.32'
def guavaVersion = '32.0.0-android'
def snakeYamlVersion = '2.0'
def jetBrainsAnnotationsVersion = '22.0.0'
def okHttpVersion = '3.12.13'
def playServicesVersion = '17.6.0'
Expand Down
32 changes: 32 additions & 0 deletions split-proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,35 @@
-dontwarn java.beans.IntrospectionException
-dontwarn java.beans.Introspector
-dontwarn java.beans.PropertyDescriptor

##---------------Begin: proguard configuration for Gson ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature

# For using GSON @Expose annotation
-keepattributes *Annotation*

# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }

# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { <fields>; }

# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer

# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}

# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
##---------------End: proguard configuration for Gson ----------
50 changes: 48 additions & 2 deletions src/androidTest/java/helper/IntegrationHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -182,16 +182,31 @@ public static SplitClientConfig lowRefreshRateConfig() {
}

public static SplitClientConfig lowRefreshRateConfig(boolean streamingEnabled) {
return lowRefreshRateConfig(streamingEnabled, false);
return lowRefreshRateConfig(streamingEnabled, false, true, 60L, 2L);
}

public static SplitClientConfig lowRefreshRateConfig(boolean streamingEnabled, boolean telemetryEnabled) {
return lowRefreshRateConfig(streamingEnabled, telemetryEnabled, true, 60L, 2L);
}

public static SplitClientConfig syncDisabledConfig() {
return lowRefreshRateConfig(true, false, false, 60L, 2L);
}

public static SplitClientConfig customSseConnectionDelayConfig(boolean streamingEnabled, long delay, long disconnectionDelay) {
return lowRefreshRateConfig(streamingEnabled, false, true, delay, disconnectionDelay);
}

public static SplitClientConfig lowRefreshRateConfig(boolean streamingEnabled, boolean telemetryEnabled, boolean syncEnabled, long delay, long sseDisconnectionDelay) {
TestableSplitConfigBuilder builder = new TestableSplitConfigBuilder()
.ready(30000)
.featuresRefreshRate(3)
.segmentsRefreshRate(3)
.impressionsRefreshRate(3)
.impressionsChunkSize(999999)
.syncEnabled(syncEnabled)
.defaultSSEConnectionDelayInSecs(delay)
.sseDisconnectionDelayInSecs(sseDisconnectionDelay)
.streamingEnabled(streamingEnabled)
.shouldRecordTelemetry(telemetryEnabled)
.enableDebug()
Expand All @@ -200,10 +215,14 @@ public static SplitClientConfig lowRefreshRateConfig(boolean streamingEnabled, b
}

public static String streamingEnabledToken() {
return streamingEnabledToken(0);
}

public static String streamingEnabledToken(int delay) {
// This token expires in 2040
return "{" +
" \"pushEnabled\": true," +
" \"connDelay\": 0," +
" \"connDelay\": " + delay + "," +
" \"token\": \"eyJhbGciOiJIUzI1NiIsImtpZCI6IjVZOU05US45QnJtR0EiLCJ0eXAiOiJKV1QifQ.eyJ4LWFibHktY2FwYWJpbGl0eSI6IntcIk16TTVOamMwT0RjeU5nPT1fTVRFeE16Z3dOamd4X01UY3dOVEkyTVRNME1nPT1fbXlTZWdtZW50c1wiOltcInN1YnNjcmliZVwiXSxcIk16TTVOamMwT0RjeU5nPT1fTVRFeE16Z3dOamd4X3NwbGl0c1wiOltcInN1YnNjcmliZVwiXSxcImNvbnRyb2xfcHJpXCI6W1wic3Vic2NyaWJlXCIsXCJjaGFubmVsLW1ldGFkYXRhOnB1Ymxpc2hlcnNcIl0sXCJjb250cm9sX3NlY1wiOltcInN1YnNjcmliZVwiLFwiY2hhbm5lbC1tZXRhZGF0YTpwdWJsaXNoZXJzXCJdfSIsIngtYWJseS1jbGllbnRJZCI6ImNsaWVudElkIiwiZXhwIjoyMjA4OTg4ODAwLCJpYXQiOjE1ODc0MDQzODh9.LcKAXnkr-CiYVxZ7l38w9i98Y-BMAv9JlGP2i92nVQY\"" +
"}";

Expand All @@ -217,6 +236,33 @@ public static String streamingEnabledV1Token() {
return "{\"connDelay\":0,\"pushEnabled\":true,\"token\":\"eyJhbGciOiJIUzI1NiIsImtpZCI6IjVZOU05US5pSGZUUmciLCJ0eXAiOiJKV1QifQ.eyJ4LWFibHktY2FwYWJpbGl0eSI6IntcIk56TTJNREk1TXpjMF9NVGd5TlRnMU1UZ3dOZz09X01Ua3pOamd3TURFNE1BPT1fbXlTZWdtZW50c1wiOltcInN1YnNjcmliZVwiXSxcIk56TTJNREk1TXpjMF9NVGd5TlRnMU1UZ3dOZz09X01qWXhNRE0yTkRjd09RPT1fbXlTZWdtZW50c1wiOltcInN1YnNjcmliZVwiXSxcIk56TTJNREk1TXpjMF9NVGd5TlRnMU1UZ3dOZz09X3NwbGl0c1wiOltcInN1YnNjcmliZVwiXSxcImNvbnRyb2xfcHJpXCI6W1wic3Vic2NyaWJlXCIsXCJjaGFubmVsLW1ldGFkYXRhOnB1Ymxpc2hlcnNcIl0sXCJjb250cm9sX3NlY1wiOltcInN1YnNjcmliZVwiLFwiY2hhbm5lbC1tZXRhZGF0YTpwdWJsaXNoZXJzXCJdfSIsIngtYWJseS1jbGllbnRJZCI6ImNsaWVudElkIiwiZXhwIjoxNjQ4NjU2MjU4LCJpYXQiOjE2NDg2NTI2NTh9.MWwudv3kafKr-gVeqt-ClLAkCngZsDhdWx-dwqM9rxs\"}";
}

public static String splitChangeV2CompressionType2() {
return splitChangeV2("9999999999999",
"1000",
"2",
"eJzMk99u2kwQxV8lOtdryQZj8N6hD5QPlThSTVNVEUKDPYZt1jZar1OlyO9emf8lVFWv2ss5zJyd82O8hTWUZSqZvW04opwhUVdsIKBSSKR+10vS1HWW7pIdz2NyBjRwHS8IXEopTLgbQqDYT+ZUm3LxlV4J4mg81LpMyKqygPRc94YeM6eQTtjphp4fegLVXvD6Qdjt9wPXF6gs2bqCxPC/2eRpDIEXpXXblpGuWCDljGptZ4bJ5lxYSJRZBoFkTcWKozpfsoH0goHfCXpB6PfcngDpVQnZEUjKIlOr2uwWqiC3zU5L1aF+3p7LFhUkPv8/mY2nk3gGgZxssmZzb8p6A9n25ktVtA9iGI3ODXunQ3HDp+AVWT6F+rZWlrWq7MN+YkSWWvuTDvkMSnNV7J6oTdl6qKTEvGnmjcCGjL2IYC/ovPYgUKnvvPtbmrmApiVryLM7p2jE++AfH6fTx09/HvuF32LWnNjStM0Xh3c8ukZcsZlEi3h8/zCObsBpJ0acqYLTmFdtqitK1V6NzrfpdPBbLmVx4uK26e27izpDu/r5yf/16AXun2Cr4u6w591xw7+LfDidLj6Mv8TXwP8xbofv/c7UmtHMmx8BAAD//0fclvU=");
}

public static String splitChangeV2CompressionType1() {
return splitChangeV2("9999999999999",
"1000",
"1",
"H4sIAAAAAAAA/8yT327aTBDFXyU612vJxoTgvUMfKB8qcaSapqoihAZ7DNusvWi9TpUiv3tl/pdQVb1qL+cwc3bOj/EGzlKeq3T6tuaYCoZEXbGFgMogkXXDIM0y31v4C/aCgMnrU9/3gl7Pp4yilMMIAuVusqDamvlXeiWIg/FAa5OSU6aEDHz/ip4wZ5Be1AmjoBsFAtVOCO56UXh31/O7ApUjV1eQGPw3HT+NIPCitG7bctIVC2ScU63d1DK5gksHCZPnEEhXVC45rosFW8ig1++GYej3g85tJEB6aSA7Aqkpc7Ws7XahCnLTbLVM7evnzalsUUHi8//j6WgyTqYQKMilK7b31tRryLa3WKiyfRCDeHhq2Dntiys+JS/J8THUt5VyrFXlHnYTQ3LU2h91yGdQVqhy+0RtTeuhUoNZ08wagTVZdxbBndF5vYVApb7z9m9pZgKaFqwhT+6coRHvg398nEweP/157Bd+S1hz6oxtm88O73B0jbhgM47nyej+YRRfgdNODDlXJWcJL9tUF5SqnRqfbtPr4LdcTHnk4rfp3buLOkG7+Pmp++vRM9w/wVblzX7Pm8OGfxf5YDKZfxh9SS6B/2Pc9t/7ja01o5k1PwIAAP//uTipVskEAAA=");
}

public static String splitChangeV2CompressionType0() {
return splitChangeV2("9999999999999",
"1000",
"0",
"eyJ0cmFmZmljVHlwZU5hbWUiOiJ1c2VyIiwiaWQiOiJkNDMxY2RkMC1iMGJlLTExZWEtOGE4MC0xNjYwYWRhOWNlMzkiLCJuYW1lIjoibWF1cm9famF2YSIsInRyYWZmaWNBbGxvY2F0aW9uIjoxMDAsInRyYWZmaWNBbGxvY2F0aW9uU2VlZCI6LTkyMzkxNDkxLCJzZWVkIjotMTc2OTM3NzYwNCwic3RhdHVzIjoiQUNUSVZFIiwia2lsbGVkIjpmYWxzZSwiZGVmYXVsdFRyZWF0bWVudCI6Im9mZiIsImNoYW5nZU51bWJlciI6MTY4NDMyOTg1NDM4NSwiYWxnbyI6MiwiY29uZmlndXJhdGlvbnMiOnt9LCJjb25kaXRpb25zIjpbeyJjb25kaXRpb25UeXBlIjoiV0hJVEVMSVNUIiwibWF0Y2hlckdyb3VwIjp7ImNvbWJpbmVyIjoiQU5EIiwibWF0Y2hlcnMiOlt7Im1hdGNoZXJUeXBlIjoiV0hJVEVMSVNUIiwibmVnYXRlIjpmYWxzZSwid2hpdGVsaXN0TWF0Y2hlckRhdGEiOnsid2hpdGVsaXN0IjpbImFkbWluIiwibWF1cm8iLCJuaWNvIl19fV19LCJwYXJ0aXRpb25zIjpbeyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjoxMDB9XSwibGFiZWwiOiJ3aGl0ZWxpc3RlZCJ9LHsiY29uZGl0aW9uVHlwZSI6IlJPTExPVVQiLCJtYXRjaGVyR3JvdXAiOnsiY29tYmluZXIiOiJBTkQiLCJtYXRjaGVycyI6W3sia2V5U2VsZWN0b3IiOnsidHJhZmZpY1R5cGUiOiJ1c2VyIn0sIm1hdGNoZXJUeXBlIjoiSU5fU0VHTUVOVCIsIm5lZ2F0ZSI6ZmFsc2UsInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjp7InNlZ21lbnROYW1lIjoibWF1ci0yIn19XX0sInBhcnRpdGlvbnMiOlt7InRyZWF0bWVudCI6Im9uIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjoxMDB9LHsidHJlYXRtZW50IjoiVjQiLCJzaXplIjowfSx7InRyZWF0bWVudCI6InY1Iiwic2l6ZSI6MH1dLCJsYWJlbCI6ImluIHNlZ21lbnQgbWF1ci0yIn0seyJjb25kaXRpb25UeXBlIjoiUk9MTE9VVCIsIm1hdGNoZXJHcm91cCI6eyJjb21iaW5lciI6IkFORCIsIm1hdGNoZXJzIjpbeyJrZXlTZWxlY3RvciI6eyJ0cmFmZmljVHlwZSI6InVzZXIifSwibWF0Y2hlclR5cGUiOiJBTExfS0VZUyIsIm5lZ2F0ZSI6ZmFsc2V9XX0sInBhcnRpdGlvbnMiOlt7InRyZWF0bWVudCI6Im9uIiwic2l6ZSI6MH0seyJ0cmVhdG1lbnQiOiJvZmYiLCJzaXplIjoxMDB9LHsidHJlYXRtZW50IjoiVjQiLCJzaXplIjowfSx7InRyZWF0bWVudCI6InY1Iiwic2l6ZSI6MH1dLCJsYWJlbCI6ImRlZmF1bHQgcnVsZSJ9XX0=");
}

private static String splitChangeV2(String changeNumber, String previousChangeNumber, String compressionType, String compressedPayload) {
return "id: vQQ61wzBRO:0:0\n" +
"event: message\n" +
"data: {\"id\":\"m2T85LA4fQ:0:0\",\"clientId\":\"pri:NzIyNjY1MzI4\",\"timestamp\":"+System.currentTimeMillis()+",\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_MTgyNTg1MTgwNg==_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":"+changeNumber+",\\\"pcn\\\":"+previousChangeNumber+",\\\"c\\\":"+compressionType+",\\\"d\\\":\\\""+compressedPayload+"\\\"}\"}\n";
}

/**
* Builds a dispatcher with the given responses.
*
Expand Down
19 changes: 17 additions & 2 deletions src/androidTest/java/helper/TestableSplitConfigBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.split.android.client.SyncConfig;
import io.split.android.client.impressions.ImpressionListener;
import io.split.android.client.network.DevelopmentSslConfig;
import io.split.android.client.service.ServiceConstants;
import io.split.android.client.service.impressions.ImpressionsMode;
import io.split.android.client.shared.UserConsent;
import io.split.android.client.utils.logger.Logger;
Expand Down Expand Up @@ -56,6 +57,8 @@ public class TestableSplitConfigBuilder {
private int mMtkRefreshRate = 1800;
private UserConsent mUserConsent = UserConsent.GRANTED;
private boolean mEncryptionEnabled;
private long mDefaultSSEConnectionDelayInSecs = ServiceConstants.DEFAULT_SSE_CONNECTION_DELAY_SECS;
private long mSSEDisconnectionDelayInSecs = 60L;

public TestableSplitConfigBuilder() {
mServiceEndpoints = ServiceEndpoints.builder().build();
Expand Down Expand Up @@ -102,7 +105,7 @@ public TestableSplitConfigBuilder ready(int ready) {
}

public TestableSplitConfigBuilder enableDebug() {
this.mLogLevel = SplitLogLevel.DEBUG;
this.mLogLevel = SplitLogLevel.VERBOSE;
return this;
}

Expand Down Expand Up @@ -236,6 +239,16 @@ public TestableSplitConfigBuilder encryptionEnabled(boolean enabled) {
return this;
}

public TestableSplitConfigBuilder defaultSSEConnectionDelayInSecs(long seconds) {
this.mDefaultSSEConnectionDelayInSecs = seconds;
return this;
}

public TestableSplitConfigBuilder sseDisconnectionDelayInSecs(long seconds) {
this.mSSEDisconnectionDelayInSecs = seconds;
return this;
}

public SplitClientConfig build() {
Constructor constructor = SplitClientConfig.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Expand Down Expand Up @@ -285,7 +298,9 @@ public SplitClientConfig build() {
mMtkPerPush,
mMtkRefreshRate,
mUserConsent,
mEncryptionEnabled);
mEncryptionEnabled,
mDefaultSSEConnectionDelayInSecs,
mSSEDisconnectionDelayInSecs);
return config;
} catch (Exception e) {
Logger.e("Error creating Testable Split client builder: "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import io.split.android.client.storage.db.MySegmentDao;
import io.split.android.client.storage.db.MySegmentEntity;
import io.split.android.client.storage.db.SplitRoomDatabase;
import io.split.android.client.telemetry.storage.InMemoryTelemetryStorage;
import io.split.android.client.utils.logger.Logger;
import tests.integration.shared.TestingData;
import tests.integration.shared.TestingHelper;
Expand All @@ -61,6 +62,7 @@ public class MySegmentsChangeV2MultiClientTest {
SplitClient mClient;
SynchronizerSpyImpl mSynchronizerSpy;
SplitRoomDatabase mDb;
private InMemoryTelemetryStorage mTelemetryStorage;

@Before
public void setup() {
Expand All @@ -71,6 +73,7 @@ public void setup() {
mMySegmentsSyncLatch2 = new CountDownLatch(1);
mMySegmentsUpdateLatch2 = new CountDownLatch(1);
mDb = DatabaseHelper.getTestDatabase(mContext);
mTelemetryStorage = new InMemoryTelemetryStorage();
}

@Test
Expand Down Expand Up @@ -102,7 +105,8 @@ public void onInvalidated(@NonNull Set<String> tables) {

mFactory = IntegrationHelper.buildFactory(
mApiKey, new Key(userKey),
config, mContext, httpClientMock, mDb, mSynchronizerSpy);
config, mContext, httpClientMock, mDb, mSynchronizerSpy,
null, null, mTelemetryStorage);

mClient = mFactory.client();
SplitClient client2 = mFactory.client(new Key(userKey2));
Expand Down Expand Up @@ -166,8 +170,8 @@ mApiKey, new Key(userKey),
Assert.assertTrue(mySegmentEntity.getSegmentList().contains("new_segment_added"));
Assert.assertFalse(mySegmentEntity.getSegmentList().contains("segment1"));

Assert.assertEquals(4, mTelemetryStorage.popUpdatesFromSSE().getMySegments());
Assert.assertEquals("new_segment_added", mySegmentEntity2.getSegmentList());

}

@After
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import io.split.android.client.network.HttpMethod;
import io.split.android.client.storage.db.MySegmentEntity;
import io.split.android.client.storage.db.SplitRoomDatabase;
import io.split.android.client.telemetry.storage.InMemoryTelemetryStorage;
import io.split.android.client.telemetry.storage.TelemetryStorage;
import io.split.android.client.utils.logger.Logger;
import fake.HttpStreamResponseMock;
import tests.integration.shared.TestingHelper;
Expand All @@ -57,6 +59,7 @@ public class MySegmentsChangeV2Test {
SplitFactory mFactory;
SplitClient mClient;
SynchronizerSpyImpl mSynchronizerSpy;
private TelemetryStorage mTelemetryStorage;

@Before
public void setup() {
Expand All @@ -68,6 +71,7 @@ public void setup() {
mApiKey = apiKeyAndDb.first;
mMySegmentsSyncLatch2 = new CountDownLatch(1);
mMySegmentsUpdateLatch2 = new CountDownLatch(1);
mTelemetryStorage = new InMemoryTelemetryStorage();
}

@Test
Expand All @@ -88,7 +92,7 @@ public void mySegmentsUpdate() throws IOException, InterruptedException {

mFactory = IntegrationHelper.buildFactory(
mApiKey, new Key(userKey),
config, mContext, httpClientMock, db, mSynchronizerSpy);
config, mContext, httpClientMock, db, mSynchronizerSpy, null, null, mTelemetryStorage);

mClient = mFactory.client();

Expand Down Expand Up @@ -125,6 +129,7 @@ mApiKey, new Key(userKey),

Assert.assertTrue(mySegmentEntity.getSegmentList().contains("new_segment_added"));
Assert.assertFalse(mySegmentEntity.getSegmentList().contains("segment1"));
Assert.assertEquals(2, mTelemetryStorage.popUpdatesFromSSE().getMySegments());
mFactory.destroy();
}

Expand Down
Loading