Skip to content
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## X.X.X
* Added a new config flag `setUseSerialExecutor(boolean useSerial)` for selecting immediate request executor type.
* Added a new config option `setWebviewDisplayOption(WebViewDisplayOption)` to control how Content and Feedback Widgets are displayed.
* `IMMERSIVE` mode (default): Full-screen display (except cutouts).
* `SAFE_AREA` mode: Omits status bar, navigation bar and cutouts when displaying webviews.

* Immediate requests now will be run by parallel executor instead of serial by default.

## 25.4.4
Expand Down
26 changes: 9 additions & 17 deletions app/src/main/java/ly/count/android/demo/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import ly.count.android.sdk.CrashData;
import ly.count.android.sdk.GlobalCrashFilterCallback;
import ly.count.android.sdk.ModuleLog;
import ly.count.android.sdk.WebViewDisplayOption;
import ly.count.android.sdk.messaging.CountlyConfigPush;
import ly.count.android.sdk.messaging.CountlyPush;

Expand Down Expand Up @@ -146,7 +147,8 @@ public void onCreate() {
Map<String, Object> customUserProperties = new ConcurrentHashMap<>();
customUserProperties.put("A", 1);

CountlyConfig config = new CountlyConfig(this, COUNTLY_APP_KEY, COUNTLY_SERVER_URL)//.setDeviceId("67567")
CountlyConfig config = new CountlyConfig(this, COUNTLY_APP_KEY, COUNTLY_SERVER_URL)
// .setDeviceId("a" + applicationStartTimestamp )
.setLoggingEnabled(true)
.setLogListener(new ModuleLog.LogCallback() {
@Override public void LogHappened(String logMessage, ModuleLog.LogLevel logLevel) {
Expand All @@ -173,57 +175,44 @@ public void onCreate() {
}
})
.enableAutomaticViewTracking()
// uncomment the line below to enable auto enrolling the user to AB experiments when downloading RC data
//.enrollABOnRCDownload()
// .setMaxRequestQueueSize(5)
//.setMaxRequestQueueSize(5)
.enableAutomaticViewShortNames()
.setGlobalViewSegmentation(automaticViewSegmentation)
.setAutomaticViewTrackingExclusions(new Class[] { ActivityExampleCustomEvents.class })

.setPushIntentAddMetadata(true)

.setLocation("us", "Böston 墨尔本", "-23.8043604,-46.6718331", "10.2.33.12")
//.setDisableLocation()

//.enableManualSessionControl()
//.enableManualSessionControlHybridMode()

//.enableTemporaryDeviceIdMode()

.setRequiresConsent(true)

//for giving all consent values
.giveAllConsents()

//in case you want to control what consent is given during init
//.setConsentEnabled(new String[] {
// Countly.CountlyFeatureNames.push, Countly.CountlyFeatureNames.sessions, Countly.CountlyFeatureNames.location,
// Countly.CountlyFeatureNames.attribution, Countly.CountlyFeatureNames.crashes, Countly.CountlyFeatureNames.events,
// Countly.CountlyFeatureNames.starRating, Countly.CountlyFeatureNames.users, Countly.CountlyFeatureNames.views,
// Countly.CountlyFeatureNames.apm, Countly.CountlyFeatureNames.remoteConfig, Countly.CountlyFeatureNames.feedback
//})

.setHttpPostForced(false)
.setParameterTamperingProtectionSalt("test-salt-checksum")
.addCustomNetworkRequestHeaders(customHeaderValues)
//.enableCertificatePinning(certificates)
//.enablePublicKeyPinning(certificates)

.RemoteConfigRegisterGlobalCallback((downloadResult, error, fullValueUpdate, downloadedValues) -> {
if (error == null) {
Log.d(Countly.TAG, "Automatic remote config download has completed. " + Countly.sharedInstance().remoteConfig().getValues());
} else {
Log.d(Countly.TAG, "Automatic remote config download encountered a problem, " + error);
}
})

.setTrackOrientationChanges(true)
//.setMetricOverride(metricOverride)

.setWebviewDisplayOption(WebViewDisplayOption.IMMERSIVE)
//.enableServerConfiguration()

.setUserProperties(customUserProperties);

// crash configuration
config.crashes
.enableCrashReporting()
.enableRecordAllThreadsWithCrash()
Expand All @@ -234,13 +223,16 @@ public void onCreate() {
}
});

// APM configuration
config.apm.enableAppStartTimeTracking()
.enableForegroundBackgroundTracking()
.setAppStartTimestampOverride(applicationStartTimestamp);


Countly.sharedInstance().init(config);
//Log.i(demoTag, "After calling init. This should return 'true', the value is:" + Countly.sharedInstance().isInitialized());

//--- PUSH NOTIFICATIONS SETUP ----//
List<String> allowedClassNames = new ArrayList<>();
allowedClassNames.add("MainActivity");
List<String> allowedPackageNames = new ArrayList<>();
Expand Down
15 changes: 15 additions & 0 deletions sdk/src/main/java/ly/count/android/sdk/CountlyConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ 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;

/**
* THIS VARIABLE SHOULD NOT BE USED
Expand Down Expand Up @@ -1055,6 +1056,20 @@ public synchronized CountlyConfig setRequestTimeoutDuration(int requestTimeoutDu
return this;
}

/**
* Set the webview display option for Content and Feedback Widgets
*
* @param displayOption IMMERSIVE for full screen with hidden system UI, or
* SAFE_AREA to use app usable area and not overlap system UI
* @return config content to chain calls
*/
public synchronized CountlyConfig setWebviewDisplayOption(WebViewDisplayOption displayOption) {
if (displayOption != null) {
this.webViewDisplayOption = displayOption;
}
return this;
}

/**
* To select the legacy AsyncTask.execute (serial executor) or
* instead executeOnExecutor(THREAD_POOL_EXECUTOR)
Expand Down
78 changes: 67 additions & 11 deletions sdk/src/main/java/ly/count/android/sdk/ModuleContent.java
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,42 @@ private String prepareContentFetchRequest(@NonNull DisplayMetrics displayMetrics
int currentOrientation = resources.getConfiguration().orientation;
boolean portrait = currentOrientation == Configuration.ORIENTATION_PORTRAIT;

int scaledWidth = (int) Math.ceil(displayMetrics.widthPixels / displayMetrics.density);
int scaledHeight = (int) Math.ceil(displayMetrics.heightPixels / displayMetrics.density);
int portraitWidth, portraitHeight, landscapeWidth, landscapeHeight;

int totalWidthPx = displayMetrics.widthPixels;
int totalHeightPx = displayMetrics.heightPixels;
int totalWidthDp = (int) Math.ceil(totalWidthPx / displayMetrics.density);
int totalHeightDp = (int) Math.ceil(totalHeightPx / displayMetrics.density);
L.d("[ModuleContent] prepareContentFetchRequest, total screen dimensions (px): [" + totalWidthPx + "x" + totalHeightPx + "], (dp): [" + totalWidthDp + "x" + totalHeightDp + "], density: [" + displayMetrics.density + "]");

WebViewDisplayOption displayOption = _cly.config_.webViewDisplayOption;
L.d("[ModuleContent] prepareContentFetchRequest, display option: [" + displayOption + "]");

if (displayOption == WebViewDisplayOption.SAFE_AREA) {
L.d("[ModuleContent] prepareContentFetchRequest, calculating safe area dimensions...");
SafeAreaDimensions safeArea = SafeAreaCalculator.calculateSafeAreaDimensions(_cly.context_, L);

// px to dp
portraitWidth = (int) Math.ceil(safeArea.portraitWidth / displayMetrics.density);
portraitHeight = (int) Math.ceil(safeArea.portraitHeight / displayMetrics.density);
landscapeWidth = (int) Math.ceil(safeArea.landscapeWidth / displayMetrics.density);
landscapeHeight = (int) Math.ceil(safeArea.landscapeHeight / displayMetrics.density);

L.d("[ModuleContent] prepareContentFetchRequest, safe area dimensions (px->dp) - Portrait: [" + safeArea.portraitWidth + "x" + safeArea.portraitHeight + " px] -> [" + portraitWidth + "x" + portraitHeight + " dp], topOffset: [" + safeArea.portraitTopOffset + " px]");
L.d("[ModuleContent] prepareContentFetchRequest, safe area dimensions (px->dp) - Landscape: [" + safeArea.landscapeWidth + "x" + safeArea.landscapeHeight + " px] -> [" + landscapeWidth + "x" + landscapeHeight + " dp], topOffset: [" + safeArea.landscapeTopOffset + " px]");
} else {
int scaledWidth = totalWidthDp;
int scaledHeight = totalHeightDp;

portraitWidth = portrait ? scaledWidth : scaledHeight;
portraitHeight = portrait ? scaledHeight : scaledWidth;
landscapeWidth = portrait ? scaledHeight : scaledWidth;
landscapeHeight = portrait ? scaledWidth : scaledHeight;

L.d("[ModuleContent] prepareContentFetchRequest, using immersive mode (full screen) dimensions (dp) - Portrait: [" + portraitWidth + "x" + portraitHeight + "], Landscape: [" + landscapeWidth + "x" + landscapeHeight + "]");
}

// this calculation needs improvement for status bar and navigation bar
int portraitWidth = portrait ? scaledWidth : scaledHeight;
int portraitHeight = portrait ? scaledHeight : scaledWidth;
int landscapeWidth = portrait ? scaledHeight : scaledWidth;
int landscapeHeight = portrait ? scaledWidth : scaledHeight;
L.i("[ModuleContent] prepareContentFetchRequest, FINAL dimensions to send to server (dp) - Portrait: [" + portraitWidth + "x" + portraitHeight + "], Landscape: [" + landscapeWidth + "x" + landscapeHeight + "]");

String language = Locale.getDefault().getLanguage().toLowerCase();
String deviceType = deviceInfo.mp.getDeviceType(_cly.context_);
Expand All @@ -219,13 +247,28 @@ Map<Integer, TransparentActivityConfig> parseContent(@NonNull JSONObject respons
JSONObject coordinates = response.optJSONObject("geo");

assert coordinates != null;
placementCoordinates.put(Configuration.ORIENTATION_PORTRAIT, extractOrientationPlacements(coordinates, displayMetrics.density, "p", content));
placementCoordinates.put(Configuration.ORIENTATION_LANDSCAPE, extractOrientationPlacements(coordinates, displayMetrics.density, "l", content));

WebViewDisplayOption displayOption = _cly.config_.webViewDisplayOption;
SafeAreaDimensions safeArea = null;

if (displayOption == WebViewDisplayOption.SAFE_AREA) {
L.d("[ModuleContent] parseContent, calculating safe area for coordinate adjustment...");
safeArea = SafeAreaCalculator.calculateSafeAreaDimensions(_cly.context_, L);
}

placementCoordinates.put(Configuration.ORIENTATION_PORTRAIT,
extractOrientationPlacements(coordinates, displayMetrics.density, "p", content,
displayOption, safeArea != null ? safeArea.portraitTopOffset : 0, safeArea != null ? safeArea.portraitLeftOffset : 0));
placementCoordinates.put(Configuration.ORIENTATION_LANDSCAPE,
extractOrientationPlacements(coordinates, displayMetrics.density, "l", content,
displayOption, safeArea != null ? safeArea.landscapeTopOffset : 0, safeArea != null ? safeArea.landscapeLeftOffset : 0));

return placementCoordinates;
}

private TransparentActivityConfig extractOrientationPlacements(@NonNull JSONObject placements, float density, @NonNull String orientation, @NonNull String content) {
private TransparentActivityConfig extractOrientationPlacements(@NonNull JSONObject placements,
float density, @NonNull String orientation, @NonNull String content,
WebViewDisplayOption displayOption, int topOffset, int leftOffset) {
if (placements.has(orientation)) {
JSONObject orientationPlacements = placements.optJSONObject(orientation);
assert orientationPlacements != null;
Expand All @@ -234,8 +277,21 @@ private TransparentActivityConfig extractOrientationPlacements(@NonNull JSONObje
int w = orientationPlacements.optInt("w");
int h = orientationPlacements.optInt("h");
L.d("[ModuleContent] extractOrientationPlacements, orientation: [" + orientation + "], x: [" + x + "], y: [" + y + "], w: [" + w + "], h: [" + h + "]");
TransparentActivityConfig config = new TransparentActivityConfig((int) Math.ceil(x * density), (int) Math.ceil(y * density), (int) Math.ceil(w * density), (int) Math.ceil(h * density));

int xPx = Math.round(x * density);
int yPx = Math.round(y * density);
int wPx = Math.round(w * density);
int hPx = Math.round(h * density);
L.d("[ModuleContent] extractOrientationPlacements, orientation: [" + orientation + "], converting dp->px: [" + w + "x" + h + " dp] -> [" + wPx + "x" + hPx + " px], density: [" + density + "]");

TransparentActivityConfig config = new TransparentActivityConfig(xPx, yPx, wPx, hPx);
config.url = content;
config.useSafeArea = (displayOption == WebViewDisplayOption.SAFE_AREA);
config.topOffset = topOffset;
config.leftOffset = leftOffset;

L.d("[ModuleContent] extractOrientationPlacements, orientation: [" + orientation + "], created config - useSafeArea: [" + config.useSafeArea + "], topOffset: [" + config.topOffset + "], leftOffset: [" + config.leftOffset + "]");

return config;
}

Expand Down
53 changes: 46 additions & 7 deletions sdk/src/main/java/ly/count/android/sdk/ModuleFeedback.java
Original file line number Diff line number Diff line change
Expand Up @@ -344,20 +344,59 @@ private void showFeedbackWidget_newActivity(@NonNull Context context, String url
int currentOrientation = resources.getConfiguration().orientation;
boolean portrait = currentOrientation == Configuration.ORIENTATION_PORTRAIT;

int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
int portraitWidth, portraitHeight, landscapeWidth, landscapeHeight;
int portraitTopOffset = 0;
int landscapeTopOffset = 0;
int portraitLeftOffset = 0;
int landscapeLeftOffset = 0;

int totalWidthPx = displayMetrics.widthPixels;
int totalHeightPx = displayMetrics.heightPixels;
L.d("[ModuleFeedback] showFeedbackWidget_newActivity, total screen dimensions (px): [" + totalWidthPx + "x" + totalHeightPx + "], density: [" + displayMetrics.density + "]");

WebViewDisplayOption displayOption = _cly.config_.webViewDisplayOption;
L.d("[ModuleFeedback] showFeedbackWidget_newActivity, display option: [" + displayOption + "]");

if (displayOption == WebViewDisplayOption.SAFE_AREA) {
L.d("[ModuleFeedback] showFeedbackWidget_newActivity, calculating safe area dimensions...");
SafeAreaDimensions safeArea = SafeAreaCalculator.calculateSafeAreaDimensions(context, L);

portraitWidth = safeArea.portraitWidth;
portraitHeight = safeArea.portraitHeight;
landscapeWidth = safeArea.landscapeWidth;
landscapeHeight = safeArea.landscapeHeight;
portraitTopOffset = safeArea.portraitTopOffset;
landscapeTopOffset = safeArea.landscapeTopOffset;
portraitLeftOffset = safeArea.portraitLeftOffset;
landscapeLeftOffset = safeArea.landscapeLeftOffset;

L.d("[ModuleFeedback] showFeedbackWidget_newActivity, safe area dimensions (px) - Portrait: [" + portraitWidth + "x" + portraitHeight + "], topOffset: [" + portraitTopOffset + "], leftOffset: [" + portraitLeftOffset + "]");
L.d("[ModuleFeedback] showFeedbackWidget_newActivity, safe area dimensions (px) - Landscape: [" + landscapeWidth + "x" + landscapeHeight + "], topOffset: [" + landscapeTopOffset + "], leftOffset: [" + landscapeLeftOffset + "]");
} else {
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;

portraitWidth = portrait ? width : height;
portraitHeight = portrait ? height : width;
landscapeWidth = portrait ? height : width;
landscapeHeight = portrait ? width : height;

L.d("[ModuleFeedback] showFeedbackWidget_newActivity, using immersive mode (full screen) dimensions (px) - Portrait: [" + portraitWidth + "x" + portraitHeight + "], Landscape: [" + landscapeWidth + "x" + landscapeHeight + "]");
}

// this calculation needs improvement for status bar and navigation bar
int portraitWidth = portrait ? width : height;
int portraitHeight = portrait ? height : width;
int landscapeWidth = portrait ? height : width;
int landscapeHeight = portrait ? width : height;
L.i("[ModuleFeedback] showFeedbackWidget_newActivity, FINAL dimensions for widget (px) - Portrait: [" + portraitWidth + "x" + portraitHeight + "], Landscape: [" + landscapeWidth + "x" + landscapeHeight + "]");

Map<Integer, TransparentActivityConfig> placementCoordinates = new ConcurrentHashMap<>();
TransparentActivityConfig pConfig = new TransparentActivityConfig(0, 0, portraitWidth, portraitHeight);
TransparentActivityConfig lConfig = new TransparentActivityConfig(0, 0, landscapeWidth, landscapeHeight);
pConfig.url = url;
lConfig.url = url;
pConfig.useSafeArea = (displayOption == WebViewDisplayOption.SAFE_AREA);
lConfig.useSafeArea = (displayOption == WebViewDisplayOption.SAFE_AREA);
pConfig.topOffset = portraitTopOffset;
lConfig.topOffset = landscapeTopOffset;
pConfig.leftOffset = portraitLeftOffset;
lConfig.leftOffset = landscapeLeftOffset;
placementCoordinates.put(Configuration.ORIENTATION_PORTRAIT, pConfig);
placementCoordinates.put(Configuration.ORIENTATION_LANDSCAPE, lConfig);

Expand Down
Loading
Loading