Skip to content

Commit 99eec8b

Browse files
authored
Merge pull request #508 from Countly/safe-area
Safe area mode
2 parents f4f04ba + bb40ad2 commit 99eec8b

File tree

10 files changed

+716
-57
lines changed

10 files changed

+716
-57
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## X.X.X
22
* Added a new config flag `setUseSerialExecutor(boolean useSerial)` for selecting immediate request executor type.
3+
* Added a new config option `setWebviewDisplayOption(WebViewDisplayOption)` to control how Content and Feedback Widgets are displayed.
4+
* `IMMERSIVE` mode (default): Full-screen display (except cutouts).
5+
* `SAFE_AREA` mode: Omits status bar, navigation bar and cutouts when displaying webviews.
6+
37
* Immediate requests now will be run by parallel executor instead of serial by default.
48

59
## 25.4.4

app/src/main/java/ly/count/android/demo/App.java

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import ly.count.android.sdk.CrashData;
2929
import ly.count.android.sdk.GlobalCrashFilterCallback;
3030
import ly.count.android.sdk.ModuleLog;
31+
import ly.count.android.sdk.WebViewDisplayOption;
3132
import ly.count.android.sdk.messaging.CountlyConfigPush;
3233
import ly.count.android.sdk.messaging.CountlyPush;
3334

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

149-
CountlyConfig config = new CountlyConfig(this, COUNTLY_APP_KEY, COUNTLY_SERVER_URL)//.setDeviceId("67567")
150+
CountlyConfig config = new CountlyConfig(this, COUNTLY_APP_KEY, COUNTLY_SERVER_URL)
151+
// .setDeviceId("a" + applicationStartTimestamp )
150152
.setLoggingEnabled(true)
151153
.setLogListener(new ModuleLog.LogCallback() {
152154
@Override public void LogHappened(String logMessage, ModuleLog.LogLevel logLevel) {
@@ -173,57 +175,44 @@ public void onCreate() {
173175
}
174176
})
175177
.enableAutomaticViewTracking()
176-
// uncomment the line below to enable auto enrolling the user to AB experiments when downloading RC data
177178
//.enrollABOnRCDownload()
178-
// .setMaxRequestQueueSize(5)
179+
//.setMaxRequestQueueSize(5)
179180
.enableAutomaticViewShortNames()
180181
.setGlobalViewSegmentation(automaticViewSegmentation)
181182
.setAutomaticViewTrackingExclusions(new Class[] { ActivityExampleCustomEvents.class })
182-
183183
.setPushIntentAddMetadata(true)
184-
185184
.setLocation("us", "Böston 墨尔本", "-23.8043604,-46.6718331", "10.2.33.12")
186185
//.setDisableLocation()
187-
188186
//.enableManualSessionControl()
189187
//.enableManualSessionControlHybridMode()
190-
191188
//.enableTemporaryDeviceIdMode()
192-
193189
.setRequiresConsent(true)
194-
195-
//for giving all consent values
196190
.giveAllConsents()
197-
198-
//in case you want to control what consent is given during init
199191
//.setConsentEnabled(new String[] {
200192
// Countly.CountlyFeatureNames.push, Countly.CountlyFeatureNames.sessions, Countly.CountlyFeatureNames.location,
201193
// Countly.CountlyFeatureNames.attribution, Countly.CountlyFeatureNames.crashes, Countly.CountlyFeatureNames.events,
202194
// Countly.CountlyFeatureNames.starRating, Countly.CountlyFeatureNames.users, Countly.CountlyFeatureNames.views,
203195
// Countly.CountlyFeatureNames.apm, Countly.CountlyFeatureNames.remoteConfig, Countly.CountlyFeatureNames.feedback
204196
//})
205-
206197
.setHttpPostForced(false)
207198
.setParameterTamperingProtectionSalt("test-salt-checksum")
208199
.addCustomNetworkRequestHeaders(customHeaderValues)
209200
//.enableCertificatePinning(certificates)
210201
//.enablePublicKeyPinning(certificates)
211-
212202
.RemoteConfigRegisterGlobalCallback((downloadResult, error, fullValueUpdate, downloadedValues) -> {
213203
if (error == null) {
214204
Log.d(Countly.TAG, "Automatic remote config download has completed. " + Countly.sharedInstance().remoteConfig().getValues());
215205
} else {
216206
Log.d(Countly.TAG, "Automatic remote config download encountered a problem, " + error);
217207
}
218208
})
219-
220209
.setTrackOrientationChanges(true)
221210
//.setMetricOverride(metricOverride)
222-
211+
.setWebviewDisplayOption(WebViewDisplayOption.IMMERSIVE)
223212
//.enableServerConfiguration()
224-
225213
.setUserProperties(customUserProperties);
226214

215+
// crash configuration
227216
config.crashes
228217
.enableCrashReporting()
229218
.enableRecordAllThreadsWithCrash()
@@ -234,13 +223,16 @@ public void onCreate() {
234223
}
235224
});
236225

226+
// APM configuration
237227
config.apm.enableAppStartTimeTracking()
238228
.enableForegroundBackgroundTracking()
239229
.setAppStartTimestampOverride(applicationStartTimestamp);
240230

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

235+
//--- PUSH NOTIFICATIONS SETUP ----//
244236
List<String> allowedClassNames = new ArrayList<>();
245237
allowedClassNames.add("MainActivity");
246238
List<String> allowedPackageNames = new ArrayList<>();

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ public class CountlyConfig {
208208

209209
// If set to true, immediate requests will use serial AsyncTask executor instead of the thread pool
210210
boolean useSerialExecutor = false;
211+
WebViewDisplayOption webViewDisplayOption = WebViewDisplayOption.IMMERSIVE;
211212

212213
/**
213214
* THIS VARIABLE SHOULD NOT BE USED
@@ -1055,6 +1056,20 @@ public synchronized CountlyConfig setRequestTimeoutDuration(int requestTimeoutDu
10551056
return this;
10561057
}
10571058

1059+
/**
1060+
* Set the webview display option for Content and Feedback Widgets
1061+
*
1062+
* @param displayOption IMMERSIVE for full screen with hidden system UI, or
1063+
* SAFE_AREA to use app usable area and not overlap system UI
1064+
* @return config content to chain calls
1065+
*/
1066+
public synchronized CountlyConfig setWebviewDisplayOption(WebViewDisplayOption displayOption) {
1067+
if (displayOption != null) {
1068+
this.webViewDisplayOption = displayOption;
1069+
}
1070+
return this;
1071+
}
1072+
10581073
/**
10591074
* To select the legacy AsyncTask.execute (serial executor) or
10601075
* instead executeOnExecutor(THREAD_POOL_EXECUTOR)

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

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,42 @@ private String prepareContentFetchRequest(@NonNull DisplayMetrics displayMetrics
190190
int currentOrientation = resources.getConfiguration().orientation;
191191
boolean portrait = currentOrientation == Configuration.ORIENTATION_PORTRAIT;
192192

193-
int scaledWidth = (int) Math.ceil(displayMetrics.widthPixels / displayMetrics.density);
194-
int scaledHeight = (int) Math.ceil(displayMetrics.heightPixels / displayMetrics.density);
193+
int portraitWidth, portraitHeight, landscapeWidth, landscapeHeight;
194+
195+
int totalWidthPx = displayMetrics.widthPixels;
196+
int totalHeightPx = displayMetrics.heightPixels;
197+
int totalWidthDp = (int) Math.ceil(totalWidthPx / displayMetrics.density);
198+
int totalHeightDp = (int) Math.ceil(totalHeightPx / displayMetrics.density);
199+
L.d("[ModuleContent] prepareContentFetchRequest, total screen dimensions (px): [" + totalWidthPx + "x" + totalHeightPx + "], (dp): [" + totalWidthDp + "x" + totalHeightDp + "], density: [" + displayMetrics.density + "]");
200+
201+
WebViewDisplayOption displayOption = _cly.config_.webViewDisplayOption;
202+
L.d("[ModuleContent] prepareContentFetchRequest, display option: [" + displayOption + "]");
203+
204+
if (displayOption == WebViewDisplayOption.SAFE_AREA) {
205+
L.d("[ModuleContent] prepareContentFetchRequest, calculating safe area dimensions...");
206+
SafeAreaDimensions safeArea = SafeAreaCalculator.calculateSafeAreaDimensions(_cly.context_, L);
207+
208+
// px to dp
209+
portraitWidth = (int) Math.ceil(safeArea.portraitWidth / displayMetrics.density);
210+
portraitHeight = (int) Math.ceil(safeArea.portraitHeight / displayMetrics.density);
211+
landscapeWidth = (int) Math.ceil(safeArea.landscapeWidth / displayMetrics.density);
212+
landscapeHeight = (int) Math.ceil(safeArea.landscapeHeight / displayMetrics.density);
213+
214+
L.d("[ModuleContent] prepareContentFetchRequest, safe area dimensions (px->dp) - Portrait: [" + safeArea.portraitWidth + "x" + safeArea.portraitHeight + " px] -> [" + portraitWidth + "x" + portraitHeight + " dp], topOffset: [" + safeArea.portraitTopOffset + " px]");
215+
L.d("[ModuleContent] prepareContentFetchRequest, safe area dimensions (px->dp) - Landscape: [" + safeArea.landscapeWidth + "x" + safeArea.landscapeHeight + " px] -> [" + landscapeWidth + "x" + landscapeHeight + " dp], topOffset: [" + safeArea.landscapeTopOffset + " px]");
216+
} else {
217+
int scaledWidth = totalWidthDp;
218+
int scaledHeight = totalHeightDp;
219+
220+
portraitWidth = portrait ? scaledWidth : scaledHeight;
221+
portraitHeight = portrait ? scaledHeight : scaledWidth;
222+
landscapeWidth = portrait ? scaledHeight : scaledWidth;
223+
landscapeHeight = portrait ? scaledWidth : scaledHeight;
224+
225+
L.d("[ModuleContent] prepareContentFetchRequest, using immersive mode (full screen) dimensions (dp) - Portrait: [" + portraitWidth + "x" + portraitHeight + "], Landscape: [" + landscapeWidth + "x" + landscapeHeight + "]");
226+
}
195227

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

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

221249
assert coordinates != null;
222-
placementCoordinates.put(Configuration.ORIENTATION_PORTRAIT, extractOrientationPlacements(coordinates, displayMetrics.density, "p", content));
223-
placementCoordinates.put(Configuration.ORIENTATION_LANDSCAPE, extractOrientationPlacements(coordinates, displayMetrics.density, "l", content));
250+
251+
WebViewDisplayOption displayOption = _cly.config_.webViewDisplayOption;
252+
SafeAreaDimensions safeArea = null;
253+
254+
if (displayOption == WebViewDisplayOption.SAFE_AREA) {
255+
L.d("[ModuleContent] parseContent, calculating safe area for coordinate adjustment...");
256+
safeArea = SafeAreaCalculator.calculateSafeAreaDimensions(_cly.context_, L);
257+
}
258+
259+
placementCoordinates.put(Configuration.ORIENTATION_PORTRAIT,
260+
extractOrientationPlacements(coordinates, displayMetrics.density, "p", content,
261+
displayOption, safeArea != null ? safeArea.portraitTopOffset : 0, safeArea != null ? safeArea.portraitLeftOffset : 0));
262+
placementCoordinates.put(Configuration.ORIENTATION_LANDSCAPE,
263+
extractOrientationPlacements(coordinates, displayMetrics.density, "l", content,
264+
displayOption, safeArea != null ? safeArea.landscapeTopOffset : 0, safeArea != null ? safeArea.landscapeLeftOffset : 0));
224265

225266
return placementCoordinates;
226267
}
227268

228-
private TransparentActivityConfig extractOrientationPlacements(@NonNull JSONObject placements, float density, @NonNull String orientation, @NonNull String content) {
269+
private TransparentActivityConfig extractOrientationPlacements(@NonNull JSONObject placements,
270+
float density, @NonNull String orientation, @NonNull String content,
271+
WebViewDisplayOption displayOption, int topOffset, int leftOffset) {
229272
if (placements.has(orientation)) {
230273
JSONObject orientationPlacements = placements.optJSONObject(orientation);
231274
assert orientationPlacements != null;
@@ -234,8 +277,21 @@ private TransparentActivityConfig extractOrientationPlacements(@NonNull JSONObje
234277
int w = orientationPlacements.optInt("w");
235278
int h = orientationPlacements.optInt("h");
236279
L.d("[ModuleContent] extractOrientationPlacements, orientation: [" + orientation + "], x: [" + x + "], y: [" + y + "], w: [" + w + "], h: [" + h + "]");
237-
TransparentActivityConfig config = new TransparentActivityConfig((int) Math.ceil(x * density), (int) Math.ceil(y * density), (int) Math.ceil(w * density), (int) Math.ceil(h * density));
280+
281+
int xPx = Math.round(x * density);
282+
int yPx = Math.round(y * density);
283+
int wPx = Math.round(w * density);
284+
int hPx = Math.round(h * density);
285+
L.d("[ModuleContent] extractOrientationPlacements, orientation: [" + orientation + "], converting dp->px: [" + w + "x" + h + " dp] -> [" + wPx + "x" + hPx + " px], density: [" + density + "]");
286+
287+
TransparentActivityConfig config = new TransparentActivityConfig(xPx, yPx, wPx, hPx);
238288
config.url = content;
289+
config.useSafeArea = (displayOption == WebViewDisplayOption.SAFE_AREA);
290+
config.topOffset = topOffset;
291+
config.leftOffset = leftOffset;
292+
293+
L.d("[ModuleContent] extractOrientationPlacements, orientation: [" + orientation + "], created config - useSafeArea: [" + config.useSafeArea + "], topOffset: [" + config.topOffset + "], leftOffset: [" + config.leftOffset + "]");
294+
239295
return config;
240296
}
241297

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

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -344,20 +344,59 @@ private void showFeedbackWidget_newActivity(@NonNull Context context, String url
344344
int currentOrientation = resources.getConfiguration().orientation;
345345
boolean portrait = currentOrientation == Configuration.ORIENTATION_PORTRAIT;
346346

347-
int width = displayMetrics.widthPixels;
348-
int height = displayMetrics.heightPixels;
347+
int portraitWidth, portraitHeight, landscapeWidth, landscapeHeight;
348+
int portraitTopOffset = 0;
349+
int landscapeTopOffset = 0;
350+
int portraitLeftOffset = 0;
351+
int landscapeLeftOffset = 0;
352+
353+
int totalWidthPx = displayMetrics.widthPixels;
354+
int totalHeightPx = displayMetrics.heightPixels;
355+
L.d("[ModuleFeedback] showFeedbackWidget_newActivity, total screen dimensions (px): [" + totalWidthPx + "x" + totalHeightPx + "], density: [" + displayMetrics.density + "]");
356+
357+
WebViewDisplayOption displayOption = _cly.config_.webViewDisplayOption;
358+
L.d("[ModuleFeedback] showFeedbackWidget_newActivity, display option: [" + displayOption + "]");
359+
360+
if (displayOption == WebViewDisplayOption.SAFE_AREA) {
361+
L.d("[ModuleFeedback] showFeedbackWidget_newActivity, calculating safe area dimensions...");
362+
SafeAreaDimensions safeArea = SafeAreaCalculator.calculateSafeAreaDimensions(context, L);
363+
364+
portraitWidth = safeArea.portraitWidth;
365+
portraitHeight = safeArea.portraitHeight;
366+
landscapeWidth = safeArea.landscapeWidth;
367+
landscapeHeight = safeArea.landscapeHeight;
368+
portraitTopOffset = safeArea.portraitTopOffset;
369+
landscapeTopOffset = safeArea.landscapeTopOffset;
370+
portraitLeftOffset = safeArea.portraitLeftOffset;
371+
landscapeLeftOffset = safeArea.landscapeLeftOffset;
372+
373+
L.d("[ModuleFeedback] showFeedbackWidget_newActivity, safe area dimensions (px) - Portrait: [" + portraitWidth + "x" + portraitHeight + "], topOffset: [" + portraitTopOffset + "], leftOffset: [" + portraitLeftOffset + "]");
374+
L.d("[ModuleFeedback] showFeedbackWidget_newActivity, safe area dimensions (px) - Landscape: [" + landscapeWidth + "x" + landscapeHeight + "], topOffset: [" + landscapeTopOffset + "], leftOffset: [" + landscapeLeftOffset + "]");
375+
} else {
376+
int width = displayMetrics.widthPixels;
377+
int height = displayMetrics.heightPixels;
378+
379+
portraitWidth = portrait ? width : height;
380+
portraitHeight = portrait ? height : width;
381+
landscapeWidth = portrait ? height : width;
382+
landscapeHeight = portrait ? width : height;
383+
384+
L.d("[ModuleFeedback] showFeedbackWidget_newActivity, using immersive mode (full screen) dimensions (px) - Portrait: [" + portraitWidth + "x" + portraitHeight + "], Landscape: [" + landscapeWidth + "x" + landscapeHeight + "]");
385+
}
349386

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

356389
Map<Integer, TransparentActivityConfig> placementCoordinates = new ConcurrentHashMap<>();
357390
TransparentActivityConfig pConfig = new TransparentActivityConfig(0, 0, portraitWidth, portraitHeight);
358391
TransparentActivityConfig lConfig = new TransparentActivityConfig(0, 0, landscapeWidth, landscapeHeight);
359392
pConfig.url = url;
360393
lConfig.url = url;
394+
pConfig.useSafeArea = (displayOption == WebViewDisplayOption.SAFE_AREA);
395+
lConfig.useSafeArea = (displayOption == WebViewDisplayOption.SAFE_AREA);
396+
pConfig.topOffset = portraitTopOffset;
397+
lConfig.topOffset = landscapeTopOffset;
398+
pConfig.leftOffset = portraitLeftOffset;
399+
lConfig.leftOffset = landscapeLeftOffset;
361400
placementCoordinates.put(Configuration.ORIENTATION_PORTRAIT, pConfig);
362401
placementCoordinates.put(Configuration.ORIENTATION_LANDSCAPE, lConfig);
363402

0 commit comments

Comments
 (0)