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

Android Fullbleed IAMs #1481

Merged
merged 17 commits into from
Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from 16 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: 2 additions & 2 deletions Examples/OneSignalDemo/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ apply plugin: 'com.onesignal.androidsdk.onesignal-gradle-plugin'
apply plugin: 'com.android.application'

android {
compileSdkVersion 28
compileSdkVersion 30
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
targetSdkVersion 30
versionCode 1
versionName "1.0"
multiDexEnabled true
Expand Down
4 changes: 2 additions & 2 deletions OneSignalSDK/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ buildscript {

ext {
buildVersions = [
compileSdkVersion: 29,
targetSdkVersion: 28
compileSdkVersion: 30,
targetSdkVersion: 30
]
androidGradlePluginVersion = '3.6.2'
androidConcurrentFutures = '1.1.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.onesignal;



import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
Expand Down Expand Up @@ -76,6 +78,7 @@ interface InAppMessageViewListener {
private boolean shouldDismissWhenActive = false;
private boolean isDragging = false;
private boolean disableDragDismiss = false;
private OSInAppMessageContent messageContent;
@NonNull private WebViewManager.Position displayLocation;
private WebView webView;
private RelativeLayout parentRelativeLayout;
Expand All @@ -91,6 +94,7 @@ interface InAppMessageViewListener {
this.displayDuration = content.getDisplayDuration() == null ? 0 : content.getDisplayDuration();
this.hasBackground = !displayLocation.isBanner();
this.disableDragDismiss = disableDragDismiss;
this.messageContent = content;
setMarginsFromContent(content);
}

Expand Down Expand Up @@ -274,13 +278,18 @@ public void run() {
* @param parentRelativeLayout root layout to attach to the pop up window
*/
private void createPopupWindow(@NonNull RelativeLayout parentRelativeLayout) {

popupWindow = new PopupWindow(
parentRelativeLayout,
hasBackground ? WindowManager.LayoutParams.MATCH_PARENT : pageWidth,
hasBackground ? WindowManager.LayoutParams.MATCH_PARENT : WindowManager.LayoutParams.WRAP_CONTENT
hasBackground ? WindowManager.LayoutParams.MATCH_PARENT : WindowManager.LayoutParams.WRAP_CONTENT,
true
);

popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
popupWindow.setTouchable(true);
// NOTE: This is required for getting fullscreen under notches working in portrait mode
popupWindow.setClippingEnabled(false);

int gravity = 0;
if (!hasBackground) {
Expand All @@ -298,11 +307,14 @@ private void createPopupWindow(@NonNull RelativeLayout parentRelativeLayout) {
}
}

// Using this instead of TYPE_APPLICATION_PANEL so the layout background does not get
// cut off in immersive mode.
// Using panel for fullbleed IAMs and dialog for non-fullbleed. The attached dialog type
// does not allow content to bleed under notches but panel does.
int displayType = this.messageContent.isFullBleed() ?
WindowManager.LayoutParams.TYPE_APPLICATION_PANEL :
WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
PopupWindowCompat.setWindowLayoutType(
popupWindow,
WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG
displayType
);

popupWindow.showAtLocation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal open class OSInAppMessageContent constructor(jsonObject: JSONObject) {
var contentHtml: String? = null
var useHeightMargin: Boolean = true
var useWidthMargin: Boolean = true
var isFullBleed: Boolean = false
// The following properties are populated from Javascript events
var displayLocation: WebViewManager.Position? = null
var displayDuration: Double? = null
Expand All @@ -23,5 +24,6 @@ internal open class OSInAppMessageContent constructor(jsonObject: JSONObject) {
var styles: JSONObject? = jsonObject.optJSONObject(STYLES)
useHeightMargin = !(styles?.optBoolean(REMOVE_HEIGHT_MARGIN, false) ?: false)
useWidthMargin = !(styles?.optBoolean(REMOVE_WIDTH_MARGIN, false) ?: false)
isFullBleed = !useHeightMargin
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import android.os.Build;
import androidx.annotation.NonNull;
import android.util.DisplayMetrics;
import android.view.DisplayCutout;
import android.view.View;
import android.view.Window;
import android.view.WindowInsets;
Expand Down Expand Up @@ -75,6 +76,35 @@ void available(@NonNull Activity currentActivity) {
return rect;
}

static int[] getCutoutAndStatusBarInsets(@NonNull Activity activity) {
Rect frame = getWindowVisibleDisplayFrame(activity);
View contentView = activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT);
float rightInset = 0;
float leftInset = 0;
float topInset = (frame.top - contentView.getTop()) / Resources.getSystem().getDisplayMetrics().density;
float bottomInset = (contentView.getBottom() - frame.bottom) / Resources.getSystem().getDisplayMetrics().density;
// API 29 is the only version where the IAM bleeds under cutouts in immersize mode
// All other versions will not need left and right insets.
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
DisplayCutout cutout = activity.getWindowManager().getDefaultDisplay().getCutout();
if (cutout != null) {
rightInset = cutout.getSafeInsetRight() / Resources.getSystem().getDisplayMetrics().density;
leftInset = cutout.getSafeInsetLeft() / Resources.getSystem().getDisplayMetrics().density;
}
}
return new int[]{Math.round(topInset), Math.round(bottomInset), Math.round(rightInset), Math.round(leftInset)};
}

static int getFullbleedWindowWidth (@NonNull Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View decorView = activity.getWindow().getDecorView();
return decorView.getWidth();
} else {
return getWindowWidth(activity);
}
}


static int getWindowWidth(@NonNull Activity activity) {
return getWindowVisibleDisplayFrame(activity).width();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.UnsupportedEncodingException;

import static com.onesignal.OSViewUtils.dpToPx;
import static com.onesignal.OSViewUtils.getFullbleedWindowWidth;

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

private static void initInAppMessage(@NonNull final Activity currentActivity, @NonNull OSInAppMessageInternal message, @NonNull OSInAppMessageContent content) {
private static void setContentSafeAreaInsets(OSInAppMessageContent content, @NonNull final Activity activity) {
String html = content.getContentHtml();
String safeAreaInsetsScript = OSJavaScriptInterface.SET_SAFE_AREA_INSETS_SCRIPT;
int[] insets = OSViewUtils.getCutoutAndStatusBarInsets(activity);
String safeAreaJSObject = String.format(OSJavaScriptInterface.SAFE_AREA_JS_OBJECT, insets[0] ,insets[1],insets[2],insets[3]);
safeAreaInsetsScript = String.format(safeAreaInsetsScript, safeAreaJSObject);
html += safeAreaInsetsScript;
content.setContentHtml(html);
}

private static void initInAppMessage(@NonNull final Activity currentActivity, @NonNull OSInAppMessageInternal message, @NonNull final OSInAppMessageContent content) {
if (content.isFullBleed()) {
setContentSafeAreaInsets(content, currentActivity);
}
try {
final String base64Str = Base64.encodeToString(
content.getContentHtml().getBytes("UTF-8"),
Expand All @@ -148,7 +162,7 @@ private static void initInAppMessage(@NonNull final Activity currentActivity, @N
public void run() {
// Handles exception "MissingWebViewPackageException: Failed to load WebView provider: No WebView installed"
try {
webViewManager.setupWebView(currentActivity, base64Str);
webViewManager.setupWebView(currentActivity, base64Str, content.isFullBleed());
} catch (Exception e) {
// Need to check error message to only catch MissingWebViewPackageException as it isn't public
if (e.getMessage() != null && e.getMessage().contains("No WebView installed")) {
Expand All @@ -170,9 +184,21 @@ class OSJavaScriptInterface {

static final String JS_OBJ_NAME = "OSAndroid";
static final String GET_PAGE_META_DATA_JS_FUNCTION = "getPageMetaData()";
static final String SET_SAFE_AREA_INSETS_JS_FUNCTION = "setSafeAreaInsets(%s)";
static final String SAFE_AREA_JS_OBJECT = "{\n" +
" top: %d,\n" +
" bottom: %d,\n" +
" right: %d,\n" +
" left: %d,\n" +
"}";
static final String SET_SAFE_AREA_INSETS_SCRIPT = "\n\n" +
"<script>\n" +
" setSafeAreaInsets(%s);\n" +
"</script>";

static final String EVENT_TYPE_KEY = "type";
static final String EVENT_TYPE_RENDERING_COMPLETE = "rendering_complete";
static final String EVENT_TYPE_RESIZE = "resize";
static final String EVENT_TYPE_ACTION_TAKEN = "action_taken";
static final String EVENT_TYPE_PAGE_CHANGE = "page_change";

Expand All @@ -197,6 +223,9 @@ public void postMessage(String message) {
if (!messageView.isDragging())
handleActionTaken(jsonObject);
break;
case EVENT_TYPE_RESIZE:
handleResize();
break;
case EVENT_TYPE_PAGE_CHANGE:
handlePageChange(jsonObject);
break;
Expand All @@ -208,6 +237,12 @@ public void postMessage(String message) {
}
}

private void handleResize() {
if (messageContent.isFullBleed()) {
updateSafeAreaInsets();
}
}

private void handleRenderComplete(JSONObject jsonObject) {
Position displayType = getDisplayLocation(jsonObject);
int pageHeight = displayType == Position.FULL_SCREEN ? -1 : getPageHeightData(jsonObject);
Expand All @@ -219,7 +254,7 @@ private void handleRenderComplete(JSONObject jsonObject) {

private int getPageHeightData(JSONObject jsonObject) {
try {
return WebViewManager.pageRectToViewHeight(activity, jsonObject.getJSONObject(IAM_PAGE_META_DATA_KEY));
return pageRectToViewHeight(activity, jsonObject.getJSONObject(IAM_PAGE_META_DATA_KEY));
} catch (JSONException e) {
return -1;
}
Expand Down Expand Up @@ -266,7 +301,7 @@ private void handlePageChange(JSONObject jsonObject) throws JSONException {
}
}

private static int pageRectToViewHeight(final @NonNull Activity activity, @NonNull JSONObject jsonObject) {
private int pageRectToViewHeight(final @NonNull Activity activity, @NonNull JSONObject jsonObject) {
try {
int pageHeight = jsonObject.getJSONObject("rect").getInt("height");
int pxHeight = OSViewUtils.dpToPx(pageHeight);
Expand All @@ -285,14 +320,26 @@ private static int pageRectToViewHeight(final @NonNull Activity activity, @NonNu
}
}

private void updateSafeAreaInsets() {
OSUtils.runOnMainUIThread(new Runnable() {
@Override
public void run() {
int[] insets = OSViewUtils.getCutoutAndStatusBarInsets(activity);
String safeAreaInsetsObject = String.format(OSJavaScriptInterface.SAFE_AREA_JS_OBJECT, insets[0], insets[1], insets[2], insets[3]);
String safeAreaInsetsFunction = String.format(OSJavaScriptInterface.SET_SAFE_AREA_INSETS_JS_FUNCTION, safeAreaInsetsObject);
webView.evaluateJavascript(safeAreaInsetsFunction, null);
}
});
}

// Every time an Activity is shown we update the height of the WebView since the available
// screen size may have changed. (Expect for Fullscreen)
private void calculateHeightAndShowWebViewAfterNewActivity() {
if (messageView == null)
return;

// Don't need a CSS / HTML height update for fullscreen
if (messageView.getDisplayPosition() == Position.FULL_SCREEN) {
// Don't need a CSS / HTML height update for fullscreen unless its fullbleed
if (messageView.getDisplayPosition() == Position.FULL_SCREEN && !messageContent.isFullBleed()) {
showMessageView(null);
return;
}
Expand All @@ -306,6 +353,10 @@ public void run() {
// At time point the webView isn't attached to a view
// Set the WebView to the max screen size then run JS to evaluate the height.
setWebViewToMaxSize(activity);
if (messageContent.isFullBleed()) {
updateSafeAreaInsets();
}

webView.evaluateJavascript(OSJavaScriptInterface.GET_PAGE_META_DATA_JS_FUNCTION, new ValueCallback<String>() {
@Override
public void onReceiveValue(final String value) {
Expand Down Expand Up @@ -373,7 +424,7 @@ private void showMessageView(@Nullable Integer newHeight) {
}

@SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
private void setupWebView(@NonNull final Activity currentActivity, final @NonNull String base64Message) {
private void setupWebView(@NonNull final Activity currentActivity, final @NonNull String base64Message, final boolean isFullScreen) {
enableWebViewRemoteDebugging();

webView = new OSWebView(currentActivity);
Expand All @@ -385,7 +436,14 @@ private void setupWebView(@NonNull final Activity currentActivity, final @NonNul

// Setup receiver for page events / data from JS
webView.addJavascriptInterface(new OSJavaScriptInterface(), OSJavaScriptInterface.JS_OBJ_NAME);

if (isFullScreen) {
webView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
webView.setFitsSystemWindows(false);
}
}
blurryRenderingWebViewForKitKatWorkAround(webView);

OSViewUtils.decorViewReady(currentActivity, new Runnable() {
Expand Down Expand Up @@ -457,12 +515,17 @@ private static void enableWebViewRemoteDebugging() {
}
}

private static int getWebViewMaxSizeX(Activity activity) {
return OSViewUtils.getWindowWidth(activity) - (MARGIN_PX_SIZE * 2);
private int getWebViewMaxSizeX(Activity activity) {
if (messageContent.isFullBleed()) {
return getFullbleedWindowWidth(activity);
}
int margin = (MARGIN_PX_SIZE * 2);
return OSViewUtils.getWindowWidth(activity) - margin;
}

private static int getWebViewMaxSizeY(Activity activity) {
return OSViewUtils.getWindowHeight(activity) - (MARGIN_PX_SIZE * 2);
private int getWebViewMaxSizeY(Activity activity) {
int margin = messageContent.isFullBleed() ? 0 : (MARGIN_PX_SIZE * 2);
return OSViewUtils.getWindowHeight(activity) - margin;
}

private void removeActivityListener() {
Expand Down