Skip to content

Commit 1fcacef

Browse files
authored
Merge pull request #1481 from OneSignal/feature/full_bleed_IAM_notch_working_snapshot
Android Fullbleed IAMs
2 parents 818e9b5 + 5b30875 commit 1fcacef

File tree

6 files changed

+120
-20
lines changed

6 files changed

+120
-20
lines changed

Examples/OneSignalDemo/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ apply plugin: 'com.onesignal.androidsdk.onesignal-gradle-plugin'
22
apply plugin: 'com.android.application'
33

44
android {
5-
compileSdkVersion 28
5+
compileSdkVersion 30
66
defaultConfig {
77
minSdkVersion 16
8-
targetSdkVersion 28
8+
targetSdkVersion 30
99
versionCode 1
1010
versionName "1.0"
1111
multiDexEnabled true

OneSignalSDK/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ buildscript {
44

55
ext {
66
buildVersions = [
7-
compileSdkVersion: 29,
8-
targetSdkVersion: 28
7+
compileSdkVersion: 30,
8+
targetSdkVersion: 30
99
]
1010
androidGradlePluginVersion = '3.6.2'
1111
androidConcurrentFutures = '1.1.0'

OneSignalSDK/onesignal/src/main/java/com/onesignal/InAppMessageView.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.onesignal;
22

3+
4+
35
import android.animation.Animator;
46
import android.animation.AnimatorListenerAdapter;
57
import android.animation.ValueAnimator;
@@ -76,6 +78,7 @@ interface InAppMessageViewListener {
7678
private boolean shouldDismissWhenActive = false;
7779
private boolean isDragging = false;
7880
private boolean disableDragDismiss = false;
81+
private OSInAppMessageContent messageContent;
7982
@NonNull private WebViewManager.Position displayLocation;
8083
private WebView webView;
8184
private RelativeLayout parentRelativeLayout;
@@ -91,6 +94,7 @@ interface InAppMessageViewListener {
9194
this.displayDuration = content.getDisplayDuration() == null ? 0 : content.getDisplayDuration();
9295
this.hasBackground = !displayLocation.isBanner();
9396
this.disableDragDismiss = disableDragDismiss;
97+
this.messageContent = content;
9498
setMarginsFromContent(content);
9599
}
96100

@@ -274,13 +278,18 @@ public void run() {
274278
* @param parentRelativeLayout root layout to attach to the pop up window
275279
*/
276280
private void createPopupWindow(@NonNull RelativeLayout parentRelativeLayout) {
281+
277282
popupWindow = new PopupWindow(
278283
parentRelativeLayout,
279284
hasBackground ? WindowManager.LayoutParams.MATCH_PARENT : pageWidth,
280-
hasBackground ? WindowManager.LayoutParams.MATCH_PARENT : WindowManager.LayoutParams.WRAP_CONTENT
285+
hasBackground ? WindowManager.LayoutParams.MATCH_PARENT : WindowManager.LayoutParams.WRAP_CONTENT,
286+
true
281287
);
288+
282289
popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
283290
popupWindow.setTouchable(true);
291+
// NOTE: This is required for getting fullscreen under notches working in portrait mode
292+
popupWindow.setClippingEnabled(false);
284293

285294
int gravity = 0;
286295
if (!hasBackground) {
@@ -298,11 +307,14 @@ private void createPopupWindow(@NonNull RelativeLayout parentRelativeLayout) {
298307
}
299308
}
300309

301-
// Using this instead of TYPE_APPLICATION_PANEL so the layout background does not get
302-
// cut off in immersive mode.
310+
// Using panel for fullbleed IAMs and dialog for non-fullbleed. The attached dialog type
311+
// does not allow content to bleed under notches but panel does.
312+
int displayType = this.messageContent.isFullBleed() ?
313+
WindowManager.LayoutParams.TYPE_APPLICATION_PANEL :
314+
WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
303315
PopupWindowCompat.setWindowLayoutType(
304316
popupWindow,
305-
WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG
317+
displayType
306318
);
307319

308320
popupWindow.showAtLocation(

OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageContent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ internal open class OSInAppMessageContent constructor(jsonObject: JSONObject) {
1212
var contentHtml: String? = null
1313
var useHeightMargin: Boolean = true
1414
var useWidthMargin: Boolean = true
15+
var isFullBleed: Boolean = false
1516
// The following properties are populated from Javascript events
1617
var displayLocation: WebViewManager.Position? = null
1718
var displayDuration: Double? = null
@@ -23,5 +24,6 @@ internal open class OSInAppMessageContent constructor(jsonObject: JSONObject) {
2324
var styles: JSONObject? = jsonObject.optJSONObject(STYLES)
2425
useHeightMargin = !(styles?.optBoolean(REMOVE_HEIGHT_MARGIN, false) ?: false)
2526
useWidthMargin = !(styles?.optBoolean(REMOVE_WIDTH_MARGIN, false) ?: false)
27+
isFullBleed = !useHeightMargin
2628
}
2729
}

OneSignalSDK/onesignal/src/main/java/com/onesignal/OSViewUtils.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import android.os.Build;
1010
import androidx.annotation.NonNull;
1111
import android.util.DisplayMetrics;
12+
import android.view.DisplayCutout;
1213
import android.view.View;
1314
import android.view.Window;
1415
import android.view.WindowInsets;
@@ -75,6 +76,35 @@ void available(@NonNull Activity currentActivity) {
7576
return rect;
7677
}
7778

79+
static int[] getCutoutAndStatusBarInsets(@NonNull Activity activity) {
80+
Rect frame = getWindowVisibleDisplayFrame(activity);
81+
View contentView = activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT);
82+
float rightInset = 0;
83+
float leftInset = 0;
84+
float topInset = (frame.top - contentView.getTop()) / Resources.getSystem().getDisplayMetrics().density;
85+
float bottomInset = (contentView.getBottom() - frame.bottom) / Resources.getSystem().getDisplayMetrics().density;
86+
// API 29 is the only version where the IAM bleeds under cutouts in immersize mode
87+
// All other versions will not need left and right insets.
88+
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
89+
DisplayCutout cutout = activity.getWindowManager().getDefaultDisplay().getCutout();
90+
if (cutout != null) {
91+
rightInset = cutout.getSafeInsetRight() / Resources.getSystem().getDisplayMetrics().density;
92+
leftInset = cutout.getSafeInsetLeft() / Resources.getSystem().getDisplayMetrics().density;
93+
}
94+
}
95+
return new int[]{Math.round(topInset), Math.round(bottomInset), Math.round(rightInset), Math.round(leftInset)};
96+
}
97+
98+
static int getFullbleedWindowWidth (@NonNull Activity activity) {
99+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
100+
View decorView = activity.getWindow().getDecorView();
101+
return decorView.getWidth();
102+
} else {
103+
return getWindowWidth(activity);
104+
}
105+
}
106+
107+
78108
static int getWindowWidth(@NonNull Activity activity) {
79109
return getWindowVisibleDisplayFrame(activity).width();
80110
}

OneSignalSDK/onesignal/src/main/java/com/onesignal/WebViewManager.java

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.UnsupportedEncodingException;
2222

2323
import static com.onesignal.OSViewUtils.dpToPx;
24+
import static com.onesignal.OSViewUtils.getFullbleedWindowWidth;
2425

2526
// Manages WebView instances by pre-loading them, displaying them, and closing them when dismissed.
2627
// Includes a static map for pre-loading, showing, and dismissed so these events can't be duplicated.
@@ -132,7 +133,20 @@ static void dismissCurrentInAppMessage() {
132133
}
133134
}
134135

135-
private static void initInAppMessage(@NonNull final Activity currentActivity, @NonNull OSInAppMessageInternal message, @NonNull OSInAppMessageContent content) {
136+
private static void setContentSafeAreaInsets(OSInAppMessageContent content, @NonNull final Activity activity) {
137+
String html = content.getContentHtml();
138+
String safeAreaInsetsScript = OSJavaScriptInterface.SET_SAFE_AREA_INSETS_SCRIPT;
139+
int[] insets = OSViewUtils.getCutoutAndStatusBarInsets(activity);
140+
String safeAreaJSObject = String.format(OSJavaScriptInterface.SAFE_AREA_JS_OBJECT, insets[0] ,insets[1],insets[2],insets[3]);
141+
safeAreaInsetsScript = String.format(safeAreaInsetsScript, safeAreaJSObject);
142+
html += safeAreaInsetsScript;
143+
content.setContentHtml(html);
144+
}
145+
146+
private static void initInAppMessage(@NonNull final Activity currentActivity, @NonNull OSInAppMessageInternal message, @NonNull final OSInAppMessageContent content) {
147+
if (content.isFullBleed()) {
148+
setContentSafeAreaInsets(content, currentActivity);
149+
}
136150
try {
137151
final String base64Str = Base64.encodeToString(
138152
content.getContentHtml().getBytes("UTF-8"),
@@ -148,7 +162,7 @@ private static void initInAppMessage(@NonNull final Activity currentActivity, @N
148162
public void run() {
149163
// Handles exception "MissingWebViewPackageException: Failed to load WebView provider: No WebView installed"
150164
try {
151-
webViewManager.setupWebView(currentActivity, base64Str);
165+
webViewManager.setupWebView(currentActivity, base64Str, content.isFullBleed());
152166
} catch (Exception e) {
153167
// Need to check error message to only catch MissingWebViewPackageException as it isn't public
154168
if (e.getMessage() != null && e.getMessage().contains("No WebView installed")) {
@@ -170,9 +184,21 @@ class OSJavaScriptInterface {
170184

171185
static final String JS_OBJ_NAME = "OSAndroid";
172186
static final String GET_PAGE_META_DATA_JS_FUNCTION = "getPageMetaData()";
187+
static final String SET_SAFE_AREA_INSETS_JS_FUNCTION = "setSafeAreaInsets(%s)";
188+
static final String SAFE_AREA_JS_OBJECT = "{\n" +
189+
" top: %d,\n" +
190+
" bottom: %d,\n" +
191+
" right: %d,\n" +
192+
" left: %d,\n" +
193+
"}";
194+
static final String SET_SAFE_AREA_INSETS_SCRIPT = "\n\n" +
195+
"<script>\n" +
196+
" setSafeAreaInsets(%s);\n" +
197+
"</script>";
173198

174199
static final String EVENT_TYPE_KEY = "type";
175200
static final String EVENT_TYPE_RENDERING_COMPLETE = "rendering_complete";
201+
static final String EVENT_TYPE_RESIZE = "resize";
176202
static final String EVENT_TYPE_ACTION_TAKEN = "action_taken";
177203
static final String EVENT_TYPE_PAGE_CHANGE = "page_change";
178204

@@ -197,6 +223,8 @@ public void postMessage(String message) {
197223
if (!messageView.isDragging())
198224
handleActionTaken(jsonObject);
199225
break;
226+
case EVENT_TYPE_RESIZE:
227+
break;
200228
case EVENT_TYPE_PAGE_CHANGE:
201229
handlePageChange(jsonObject);
202230
break;
@@ -219,7 +247,7 @@ private void handleRenderComplete(JSONObject jsonObject) {
219247

220248
private int getPageHeightData(JSONObject jsonObject) {
221249
try {
222-
return WebViewManager.pageRectToViewHeight(activity, jsonObject.getJSONObject(IAM_PAGE_META_DATA_KEY));
250+
return pageRectToViewHeight(activity, jsonObject.getJSONObject(IAM_PAGE_META_DATA_KEY));
223251
} catch (JSONException e) {
224252
return -1;
225253
}
@@ -266,7 +294,7 @@ private void handlePageChange(JSONObject jsonObject) throws JSONException {
266294
}
267295
}
268296

269-
private static int pageRectToViewHeight(final @NonNull Activity activity, @NonNull JSONObject jsonObject) {
297+
private int pageRectToViewHeight(final @NonNull Activity activity, @NonNull JSONObject jsonObject) {
270298
try {
271299
int pageHeight = jsonObject.getJSONObject("rect").getInt("height");
272300
int pxHeight = OSViewUtils.dpToPx(pageHeight);
@@ -285,14 +313,26 @@ private static int pageRectToViewHeight(final @NonNull Activity activity, @NonNu
285313
}
286314
}
287315

316+
private void updateSafeAreaInsets() {
317+
OSUtils.runOnMainUIThread(new Runnable() {
318+
@Override
319+
public void run() {
320+
int[] insets = OSViewUtils.getCutoutAndStatusBarInsets(activity);
321+
String safeAreaInsetsObject = String.format(OSJavaScriptInterface.SAFE_AREA_JS_OBJECT, insets[0], insets[1], insets[2], insets[3]);
322+
String safeAreaInsetsFunction = String.format(OSJavaScriptInterface.SET_SAFE_AREA_INSETS_JS_FUNCTION, safeAreaInsetsObject);
323+
webView.evaluateJavascript(safeAreaInsetsFunction, null);
324+
}
325+
});
326+
}
327+
288328
// Every time an Activity is shown we update the height of the WebView since the available
289329
// screen size may have changed. (Expect for Fullscreen)
290330
private void calculateHeightAndShowWebViewAfterNewActivity() {
291331
if (messageView == null)
292332
return;
293333

294-
// Don't need a CSS / HTML height update for fullscreen
295-
if (messageView.getDisplayPosition() == Position.FULL_SCREEN) {
334+
// Don't need a CSS / HTML height update for fullscreen unless its fullbleed
335+
if (messageView.getDisplayPosition() == Position.FULL_SCREEN && !messageContent.isFullBleed()) {
296336
showMessageView(null);
297337
return;
298338
}
@@ -306,6 +346,10 @@ public void run() {
306346
// At time point the webView isn't attached to a view
307347
// Set the WebView to the max screen size then run JS to evaluate the height.
308348
setWebViewToMaxSize(activity);
349+
if (messageContent.isFullBleed()) {
350+
updateSafeAreaInsets();
351+
}
352+
309353
webView.evaluateJavascript(OSJavaScriptInterface.GET_PAGE_META_DATA_JS_FUNCTION, new ValueCallback<String>() {
310354
@Override
311355
public void onReceiveValue(final String value) {
@@ -373,7 +417,7 @@ private void showMessageView(@Nullable Integer newHeight) {
373417
}
374418

375419
@SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
376-
private void setupWebView(@NonNull final Activity currentActivity, final @NonNull String base64Message) {
420+
private void setupWebView(@NonNull final Activity currentActivity, final @NonNull String base64Message, final boolean isFullScreen) {
377421
enableWebViewRemoteDebugging();
378422

379423
webView = new OSWebView(currentActivity);
@@ -385,7 +429,14 @@ private void setupWebView(@NonNull final Activity currentActivity, final @NonNul
385429

386430
// Setup receiver for page events / data from JS
387431
webView.addJavascriptInterface(new OSJavaScriptInterface(), OSJavaScriptInterface.JS_OBJ_NAME);
388-
432+
if (isFullScreen) {
433+
webView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
434+
View.SYSTEM_UI_FLAG_IMMERSIVE |
435+
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
436+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
437+
webView.setFitsSystemWindows(false);
438+
}
439+
}
389440
blurryRenderingWebViewForKitKatWorkAround(webView);
390441

391442
OSViewUtils.decorViewReady(currentActivity, new Runnable() {
@@ -457,12 +508,17 @@ private static void enableWebViewRemoteDebugging() {
457508
}
458509
}
459510

460-
private static int getWebViewMaxSizeX(Activity activity) {
461-
return OSViewUtils.getWindowWidth(activity) - (MARGIN_PX_SIZE * 2);
511+
private int getWebViewMaxSizeX(Activity activity) {
512+
if (messageContent.isFullBleed()) {
513+
return getFullbleedWindowWidth(activity);
514+
}
515+
int margin = (MARGIN_PX_SIZE * 2);
516+
return OSViewUtils.getWindowWidth(activity) - margin;
462517
}
463518

464-
private static int getWebViewMaxSizeY(Activity activity) {
465-
return OSViewUtils.getWindowHeight(activity) - (MARGIN_PX_SIZE * 2);
519+
private int getWebViewMaxSizeY(Activity activity) {
520+
int margin = messageContent.isFullBleed() ? 0 : (MARGIN_PX_SIZE * 2);
521+
return OSViewUtils.getWindowHeight(activity) - margin;
466522
}
467523

468524
private void removeActivityListener() {

0 commit comments

Comments
 (0)