diff --git a/.github/workflows/build_and_test_sdk.yml b/.github/workflows/build_and_test_sdk.yml index 914ffcba7..e269f18ba 100644 --- a/.github/workflows/build_and_test_sdk.yml +++ b/.github/workflows/build_and_test_sdk.yml @@ -14,10 +14,12 @@ on: branches: - master - staging + - staging-nw pull_request: branches: - master - staging + - staging-nw jobs: setup: runs-on: ubuntu-latest @@ -73,8 +75,8 @@ jobs: uses: EnricoMi/publish-unit-test-result-action@v2 if: always() with: - check_name: "Unit Test Results :rocket:" - comment_title: "Unit Test Results :rocket:" + check_name: "Unit Test Results (nw) :rocket:" + comment_title: "Unit Test Results (nw) :rocket:" files: | sdk/build/outputs/androidTest-results/**/*.xml sdk-native/build/test-results/**/*.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 789f46318..0ecb091e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ -## XX.XX.XX +## 26.1.5-nw +Released as `ly.count.android:sdk-nw:26.1.5`. See the "The `sdk-nw` artifact" section of the README for what this build changes. + +* This build reports its SDK name as `java-native-android-nw`. +* The SDK now supports API level 37. Integrating apps must build with `compileSdk` 34 or higher. +* Added a new configuration option `setCustomSSLSocketFactory(SSLSocketFactory)` to send the SDK's HTTPS requests through a custom SSLSocketFactory. * Improved the security of content, feedback widget, and push notification links by blocking the `data:`, `zip:`, and `intent:` URI schemes by default, both for opening links and for loading web view resources. They can be allowed with `setAllowedIntentSchemes(List)`. +* Mitigated issues in "ly.count.android:sdk-native" where: + * the native libraries were not laid out for 16 KB memory page sizes. + * a failure to load the "countly_native" library would crash the app instead of disabling native crash reporting. + * "CountlyNative.getBreakpadChecksum()" returned a stale revision instead of the Breakpad revision the native library was built from. + ## 26.1.5 * The SDK now supports API level 37. Integrating apps must build with `compileSdk` 34 or higher. * Added a new configuration option `setCustomSSLSocketFactory(SSLSocketFactory)` to send the SDK's HTTPS requests through a custom SSLSocketFactory. diff --git a/README.md b/README.md index c48233a73..cc3bb3c9a 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,26 @@ This SDK supports the following features: ## Security Security is very important to us. If you discover any issue regarding security, please disclose the information responsibly by sending an email to security@count.ly and **not by creating a GitHub issue**. +### The `sdk-nw` artifact +Alongside `ly.count.android:sdk`, the SDK is published as `ly.count.android:sdk-nw`, where "nw" stands for "no web view". +It exists for organizations that need a hardened baseline to hold across a large number of applications, where relying on every application to apply the same configuration is not practical. +Two settings are enforced by the artifact itself: + +* All WebView based UI is disabled, so no WebView is created or shown for the Content feature, Feedback Widgets, or the rating popup. +* The SDK's console logging is kept off in production (non-debuggable) builds, even when logging is enabled in the configuration. + +Both settings are applied during `init` and can not be turned off, so no application, wrapper, or configuration mistake can weaken them. +Everything else is identical to `ly.count.android:sdk`: the same source, the same package names, and the same API. +Switching is a one line dependency change and requires no changes to your imports or code. + +```gradle +implementation 'ly.count.android:sdk-nw:26.1.5' +``` + +Version numbers match the regular release they were built from, so `ly.count.android:sdk-nw:26.1.5` contains the same SDK as `ly.count.android:sdk:26.1.5`. +Depend on either `ly.count.android:sdk` or `ly.count.android:sdk-nw`, never both, because they contain the same classes. +Applications that need the WebView based UI, such as the Content feature or Feedback Widgets, should use `ly.count.android:sdk`. + ## Badges If you like Countly, [why not use one of our badges](https://count.ly/brand-assets) and give a link back to us so others know about this wonderful platform? diff --git a/sdk/build.gradle b/sdk/build.gradle index aadbb0d96..1ceb7fa20 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -11,6 +11,12 @@ buildscript { } } +// This is the "nw" (no WebView) branch. It carries the same source as the regular SDK with two +// settings enforced at init, WebView UI off and console logging off in production builds, neither of +// which the config can turn back on, and it always publishes as 'ly.count.android:sdk-nw'. Keep this +// branch a one way target: merge from the release branch into it, never the other way around, and do +// not cut the regular 'ly.count.android:sdk' release from here. + android { namespace 'ly.count.android.sdk' compileSdk 37 @@ -30,6 +36,32 @@ android { } testInstrumentationRunner 'ly.count.android.sdk.test.InstrumentationTestRunner' + + // These tests assert behavior this branch removes on purpose: content zone traffic, the rating + // popup, and console logging in production being on by default. Every one of them passes on the + // release branch, so they are excluded here rather than adapted. The rest of the suite, 965 of + // 983 tests, still runs. Keep this list in sync when the release branch adds content or + // WebView tests. + testInstrumentationRunnerArguments notClass: [ + 'ly.count.android.sdk.ModuleConfigurationTests#contentZoneEnabled_allFeatures', + 'ly.count.android.sdk.ModuleConfigurationTests#crashReportingDisabled_allFeatures', + 'ly.count.android.sdk.ModuleConfigurationTests#customEventTrackingDisabled_allFeatures', + 'ly.count.android.sdk.ModuleConfigurationTests#journeyTriggerEvents_contentZoneRefreshFlow', + 'ly.count.android.sdk.ModuleConfigurationTests#journeyTriggerEvents_refreshRetriesCorrectlyAfterProvidingEmptyResponse', + 'ly.count.android.sdk.ModuleConfigurationTests#journeyTriggerEvents_refreshRetryStopAfterValidContentResponse', + 'ly.count.android.sdk.ModuleConfigurationTests#journeyTriggerEvents_skipsRefreshWhenInContentZone', + 'ly.count.android.sdk.ModuleConfigurationTests#locationTrackingDisabled_allFeatures', + 'ly.count.android.sdk.ModuleConfigurationTests#refreshContentZoneDisabled_allFeatures', + 'ly.count.android.sdk.ModuleConfigurationTests#serverConfig_Defaults_AllFeatures', + 'ly.count.android.sdk.ModuleConfigurationTests#sessionsDisabled_allFeatures', + 'ly.count.android.sdk.ModuleConfigurationTests#trackingDisabled_allFeatures', + 'ly.count.android.sdk.ModuleConfigurationTests#viewTrackingDisabled_allFeatures', + 'ly.count.android.sdk.ModuleContentTests#contentZone_doesNotResumeAfterExplicitExit', + 'ly.count.android.sdk.ModuleContentTests#contentZone_resumesAfterTemporaryDeviceIDToggle', + 'ly.count.android.sdk.ModuleContentTests#previewContent_validContentId', + 'ly.count.android.sdk.ModuleLogTests#productionBuild_flagOff_loggingUnaffected', + 'ly.count.android.sdk.ModuleRatingsTests#showFeedbackPopup_blockedInTemporaryDeviceIDMode' + ].join(',') testHandleProfiling true testFunctionalTest true } @@ -58,6 +90,15 @@ android { testNamespace 'ly.count.android.sdk.test' } +//this branch is published under its own artifact id, everything else comes from gradle.properties +mavenPublishing { + coordinates(GROUP.toString(), 'sdk-nw', VERSION_NAME.toString()) + pom { + name = 'Countly Android SDK (no WebView)' + description = 'Android SDK for Countly mobile analytics, with all of the SDK\'s WebView based UI disabled and console logging kept off in production builds, enforced and not configurable' + } +} + def mockitoVersion = "4.11.0" dependencies { diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/CountlyConfigTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/CountlyConfigTests.java index 2089e410b..8bc91c2a6 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/CountlyConfigTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/CountlyConfigTests.java @@ -274,7 +274,9 @@ void assertDefaultValues(CountlyConfig config, boolean includeConstructorValues) Assert.assertNull(config.starRatingTextMessage); Assert.assertNull(config.starRatingTextTitle); Assert.assertFalse(config.loggingEnabled); - Assert.assertFalse(config.disableSDKLoggingInProduction); + //this branch enforces both, so their defaults are inverted compared to the regular SDK + Assert.assertTrue(config.disableSDKLoggingInProduction); + Assert.assertFalse(config.webViewEnabled); Assert.assertFalse(config.crashes.enableUnhandledCrashReporting); Assert.assertFalse(config.enableAutomaticViewTracking); Assert.assertFalse(config.autoTrackingUseShortName); diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java b/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java index 2a7d7782d..3dc32a57a 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java @@ -43,7 +43,8 @@ public class TestUtils { public final static String commonURL = "http://test.count.ly"; public final static String commonAppKey = "appkey"; public final static String commonDeviceId = "1234"; - public final static String SDK_NAME = "java-native-android"; + //matches what the SDK reports on this branch + public final static String SDK_NAME = "java-native-android-nw"; public final static String SDK_VERSION = "26.1.5"; public static final int MAX_THREAD_COUNT_PER_STACK_TRACE = 50; 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 1547d97df..8b497dc6c 100644 --- a/sdk/src/main/java/ly/count/android/sdk/Countly.java +++ b/sdk/src/main/java/ly/count/android/sdk/Countly.java @@ -50,8 +50,9 @@ public class Countly { private final String DEFAULT_COUNTLY_SDK_VERSION_STRING = "26.1.5"; /** * Used as request meta data on every request + * This is the "sdk-nw" branch, so the name carries the "-nw" marker to tell it apart on the server */ - private final String DEFAULT_COUNTLY_SDK_NAME = "java-native-android"; + private final String DEFAULT_COUNTLY_SDK_NAME = "java-native-android-nw"; /** * Current version of the Count.ly Android SDK as a displayable string. @@ -311,6 +312,10 @@ public synchronized Countly init(CountlyConfig config) { throw new IllegalArgumentException("Can't init SDK with 'null' config"); } + //this build enforces both settings, the config can turn them on but never off + config.webViewEnabled = false; + config.disableSDKLoggingInProduction = true; + //determine whether console logging must stay off for production builds before any logging call loggingForcedOffForProduction = shouldForceLoggingOffForProduction(config); @@ -328,6 +333,8 @@ public synchronized Countly init(CountlyConfig config) { L.d("[Init] Initializing Countly [" + COUNTLY_SDK_NAME + "] SDK version [" + COUNTLY_SDK_VERSION_STRING + "] default name[" + DEFAULT_COUNTLY_SDK_NAME + "] default version[" + DEFAULT_COUNTLY_SDK_VERSION_STRING + "]"); } + L.i("[Init] This is the 'nw' build of the SDK. All WebView based UI is disabled and console logging is kept off in production builds. Neither can be turned off"); + if (config.context == null) { if (config.application != null) { L.d("[Init] No explicit context provided. Using context from the provided application class"); diff --git a/sdk/src/main/java/ly/count/android/sdk/CountlyConfig.java b/sdk/src/main/java/ly/count/android/sdk/CountlyConfig.java index 7eb028097..a99b7e4b2 100644 --- a/sdk/src/main/java/ly/count/android/sdk/CountlyConfig.java +++ b/sdk/src/main/java/ly/count/android/sdk/CountlyConfig.java @@ -112,7 +112,8 @@ public class CountlyConfig { protected boolean loggingEnabled = false; - protected boolean disableSDKLoggingInProduction = false; + //always on in this "sdk-nw" build, Countly.init enforces it again so it can not be turned off + protected boolean disableSDKLoggingInProduction = true; protected boolean enableAutomaticViewTracking = false; @@ -217,7 +218,8 @@ public class CountlyConfig { // If set to true, immediate requests will use serial AsyncTask executor instead of the thread pool boolean useSerialExecutor = false; WebViewDisplayOption webViewDisplayOption = WebViewDisplayOption.IMMERSIVE; - boolean webViewEnabled = true; + //always off in this "sdk-nw" build, Countly.init enforces it again so it can not be turned on + boolean webViewEnabled = false; // If set to true, request queue cleaner will remove all overflow at once instead of gradually (loop limited) removing boolean disableGradualRequestCleaner = false; @@ -393,6 +395,8 @@ public synchronized CountlyConfig setLoggingEnabled(boolean enabled) { * A production build is detected as one that is not flagged debuggable in its * application info. This only affects console output. A log listener provided * through {@link #setLogListener(ModuleLog.LogCallback)} keeps receiving logs. + * The "ly.count.android:sdk-nw" artifact applies this on its own and does not allow + * it to be turned off. */ public synchronized CountlyConfig disableSDKLoggingInProduction() { this.disableSDKLoggingInProduction = true; @@ -1162,7 +1166,9 @@ public synchronized CountlyConfig setWebviewDisplayOption(WebViewDisplayOption d /** * Disable all WebView-based UI in the SDK. When called, no WebView is ever created or shown * for any feature. This covers the Content feature overlay, Feedback Widgets (surveys, NPS, - * and rating widgets), and the rating popup. WebView UI is enabled by default. + * and rating widgets), and the rating popup. WebView UI is enabled by default in the + * "ly.count.android:sdk" artifact. The "ly.count.android:sdk-nw" artifact keeps it disabled + * at all times and does not allow it to be turned back on. * * @return Returns the same config object for convenient linking */