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 11 css safe-area-insets not applied #1465

Open
3 tasks done
francois-dibulo opened this issue Jul 26, 2022 · 29 comments
Open
3 tasks done

Android 11 css safe-area-insets not applied #1465

francois-dibulo opened this issue Jul 26, 2022 · 29 comments
Labels

Comments

@francois-dibulo
Copy link

francois-dibulo commented Jul 26, 2022

Bug Report

Problem

  1. I created a clean cordova project (cordova create hello com.example.hello HelloWorld)
  2. Added cordova platform android 10.0.2
  3. Build the app on a device with a camera notch

=> The safe-area is considered.

  1. Run cordova platform remove android
  2. Run cordova platform add [email protected]
  3. Run cordova run android

=> The safe-area is not considered anymore

Only change I made in the config.xml:

<preference name="Fullscreen" value="true" />

What is expected to happen?

Using css padding safe-area-insets should add a safe-area padding

What does actually happen?

The padding for the safe-area is not added when using [email protected]

Information

Command or Code

Environment, Platform, Device

  • Huawei P30 Lite, Android
  • Google Pixel 6

Version information

cordova: 11.0.0
npm 8.11.0

Checklist

  • I searched for existing GitHub issues
  • I updated all Cordova tooling to most recent version
  • I included all the necessary information above
@breautek
Copy link
Contributor

Can you reproduce (including it working on cordova-android@10) this on an emulator or on a device that simulates notches (using the Developer Options)? In my experience, CSS safe area insets never properly worked on android, but I also don't have a physical device with notches either.

@francois-dibulo
Copy link
Author

Yes:

I used the Developer Option to simulate a notch (double cutout)

Result with android-10.1.1:

signal-2022-08-03-172744_002

(the black bar is the notch space)

Result with android-11.0.0:

signal-2022-08-03-172751_002

The notch / black bar is not considered - the css safe-area styles don't seem to get applied

I didn't change anything except changing the platforms from 10.1.1 to 11.0.0

@francois-dibulo
Copy link
Author

Sorry, but any update on this? Anything else I could try?
Thank you!

@breautek breautek added help wanted Requires Triage Issues that requires further analysis labels Aug 9, 2022
@breautek
Copy link
Contributor

breautek commented Aug 9, 2022

Sorry, but any update on this? Anything else I could try? Thank you!

Sorry, I haven't had a chance to look at this myself. I'm not really sure at this time.

I can say that Cordova doesn't implement browser features (Cordova isn't the browser, it's just a packaging tool providing resources to be used by the system's webview). So it's really strange see a browser feature suddenly not work between two different cordova-android versions and it's definitely not clear what might be the cause of it.

If someone wants to poke around, the commits since v10.1.1 can be found here

If I were to take a wild guess, cordova-android 11 implements the new splashscreen API that Android offers, for API 32 support. This involves making changes to the app theme. You could try forking cordova-android and reverting this commit to see if it changes anything. Hopefully it won't completely break the build... it may cause issues related to the splashscreen, but if it fixes the safe area inset, then we know it's something within this splashscreen PR that is causing the issue.

@ljudbane
Copy link

Here are the pictures of how this looks like on a real device.

cordova-android 10:
IMG_20220816_153102

cordova-android 11:
IMG_20220816_153309

I'm guessing the webview wasn't all the way to the top before, but it is now.

@ljudbane
Copy link

While further exploring this issue i found this resource: https://developer.android.com/guide/topics/display-cutout.

The way the cutout behaves, can be defined in manifest inside <style>. So i tried adding

<item name="android:windowLayoutInDisplayCutoutMode">
            never
</item>

to file platforms/android/app/src/main/res/values/themes.xml and i got the top black bar back. The Android studio warned me that this option was only available from some Android version on so i let it create a values-v27/themes.xml override file.

As to why the default behaviour changed, i don't know. Maybe because we target higher API version now. Or because theme on main activity was changed to @style/Theme.App.SplashScreen.

Since i need a quick solution to this i will be creating a after_platform_add hook in my project that will create a values-v27/themes.xml file in the Android project.

Maybe in the future this could be controlled with a setting in config.xml :)

@boengli
Copy link

boengli commented Aug 26, 2022

I have the same issue, but after forcing a reload, window.location.replace('/home') the save-area is set correctly and it looks good.

The above solutions <item name="android:windowLayoutInDisplayCutoutMode">never</item> works, but doesn't really look great.

I would prefer the "CUTOUT_MODE_SHORT_EDGES" but without forcing a reload.

Any update?

@boengli
Copy link

boengli commented Sep 28, 2022

Still no update? ☹️

@nijakobius
Copy link

nijakobius commented Oct 31, 2022

I have the solution!

  1. Add this to your <head>:
<style>
:root {
    --safe-area-top   : env(safe-area-inset-top);
    --safe-area-bottom: env(safe-area-inset-bottom);
}
</style>
  1. Add the StatusBar plugin cordova plugin add cordova-plugin-statusbar

  2. Add <preference name="StatusBarOverlaysWebView" value="false" /> to config.xml

  3. Add the following JavaScript:

document.addEventListener('deviceready', () => {
    var initialHeight = document.documentElement.clientHeight;
    window.addEventListener('resize', getStatusBarHeight);
    StatusBar.overlaysWebView(true);
    function getStatusBarHeight() {
        var currentHeight = document.documentElement.clientHeight;
        document.documentElement.style.setProperty('--safe-area-top', (currentHeight - initialHeight) + 'px');
        window.removeEventListener('resize', getStatusBarHeight);
    }
}, false);

This will first calculate the clientHeight WITH the StatusBar and then WITHOUT it. The difference will be saved in a CSS variable. Then, in your CSS just use var(--safe-area-top) wherever you would have used env(safe-area-inset-top) :)

For a quick test, add the following to <body> and you'll see that the red box (using env(safe-area-inset-top)) ignores the safe area while the blue box (var(--safe-area-top) respects it:

<div style="position: absolute; z-index: 2000; top: env(safe-area-inset-top); height: calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 3px); width: 5vw; background-color: rgba(255, 0, 0, .1); border: 1px solid red; "></div>
<div style="position: absolute; z-index: 2000; left: 5vw; top: var(--safe-area-top); height: calc(100vh - var(--safe-area-top) - var(--safe-area-bottom) - 3px); width: 5vw; background-color: rgba(0, 0, 255, .1); border: 1px solid blue;"></div>

Screenshot 2022-10-31 at 17 37 31

@NiklasMerz
Copy link
Member

Looks like we need to wait for Android to support the safe-area. See this Chromium Bug.

Thanks @nijakobius for providing a workaround. We could add this code with a note about this issue to the README files under the Android quirks section. What do you think?

@kovacs-tamas
Copy link

Looks like the latest Android System WebView (107.0.5304.105) solved the problem. I've checked with 108.* Beta and 109.* Dev builds also, they work well

@breautek
Copy link
Contributor

breautek commented Dec 2, 2022

Thanks @nijakobius for providing a workaround. We could add this code with a note about this issue to the README files under the Android quirks section. What do you think?

If we add a note, I'd rather just link to the comment using #1465 (comment) than have code in-pace I think. In effort to keep the README docs clean/concise.

Since it was determine that this was a system webview issue, I'll be closing this issue. Should be resolved if the device has version 107.0.5304.105 or later. If the issue persists, I suggest checking the installed Android System Webview version and ensuring you have at least version 107.0.5304.105, and if not, try updating the webview. If the issue still persists, then a comment can be made and one of us can reopen this issue.

@breautek breautek closed this as not planned Won't fix, can't repro, duplicate, stale Dec 2, 2022
@AshleyScirra
Copy link

AshleyScirra commented Dec 5, 2022

The issue still reproduces for me on a Nokia 7.1 with Android System WebView 107.0.5304.141.

This Chromium issue is still open and appears to be the underlying issue. I would not expect this to be fixed until that issue is closed. It would be nice if Cordova could provide a built-in workaround until then though.

@cxcxcxcx
Copy link

The issue reproduces for Samsung Galaxy S21, webview 108.0.5359.79

#1465 (comment) doesn't always work: my app is landscape.

#1465 (comment) works for me. It doesn't have to be a script. After themes_v27.xml is created, the following works in config.xml, android section:

<resource-file src="resources/android/xml/themes_v27.xml" target="app/src/main/res/values-v27/themes.xml" />

I think it's a decent fix because the behavior is consistent with previous versions. And it can always be improved once the upstream Chromium bug is fixed.

@breautek
Copy link
Contributor

breautek commented Dec 15, 2022

I think using "android:windowLayoutInDisplayCutoutMode"="never" is not really a great fix from a Cordova standpoint. e.g. it's not something we can apply generically.

From my understanding based on the Android Docs, never makes it so the app never consumes the cutout/unsafe space. Which is why it kind of fixes the issue but it also strips control away and I assume it produces blank space in the unsafe areas, likely black. This is why I think we cannot apply this generically The blank space color might be configurable via the BackgroundColor preference.

We could add a preference to configure this, but if a workaround can be done simply by providing a resource-file, I think I'd prefer documenting that approach rather than adding a feature option that we would have to maintain thereafter, especially since this feature is just for a workaround to an upstream chromium bug.

@cxcxcxcx If you could provide a full example of the workaround including the contents of resources/android/xml/themes_v27.xml, I think that would be helpful. I find how to apply the workaround from #1465 (comment) is not completely clear. Later I can take it and document it to the Android Quirks section.

@cxcxcxcx
Copy link

cxcxcxcx commented Dec 15, 2022

The blank space color might be configurable via the BackgroundColor preference.

For my device, it's black, the same color as the phone edge. I didn't notice it until I upgraded to cordova-android 11. It's probably a fallback mechanism that phone manufactuers considered to support older apps.

We could add a preference to configure this, but if a workaround can be done simply by providing a resource-file, I think I'd prefer documenting that approach rather than adding a feature option that we would have to maintain thereafter, especially since this feature is just for a workaround to an upstream chromium bug.

Yeah, it's weird to workaround an upstream bug. However, because cordova is largely about enabling HTML5 on native apps, the upstream bug sounds a deal-breaker: almost all apps will suffer, but developers may not realize just because they don't have test devices with cutouts.

Forcing not using the cutout space is not ideal, but it's reverting to the cordova-android 10 behavior. Power users can still customize using hooks, and the default behavior will be decent.

@cxcxcxcx If you could provide a full example of the workaround including the contents of resources/android/xml/themes_v27.xml, I think that would be helpful. I find how to apply the workaround from #1465 (comment) is not completely clear. Later I can take it and document it to the Android Quirks section.

I cloned the themes.xml file:

$ cat resources/android/xml/themes_v27.xml 
<?xml version='1.0' encoding='utf-8'?>
<resources>
    <style name="Theme.App.SplashScreen" parent="Theme.SplashScreen.IconBackground">
        <item name="windowSplashScreenBackground">@color/cdv_splashscreen_background</item>
        <item name="windowSplashScreenAnimatedIcon">@drawable/ic_cdv_splashscreen</item>
        <item name="windowSplashScreenAnimationDuration">200</item>
        <item name="postSplashScreenTheme">@style/Theme.AppCompat.NoActionBar</item>
        <item name="android:windowLayoutInDisplayCutoutMode">never</item>
    </style>
</resources>

@breautek
Copy link
Contributor

Ah yah, if you're cloning the theme... that kinda makes me want to add a preference. That workaround creates a high risk of breakage since it depends on you kinda re-implementing things required for Cordova, which if Cordova changes the splashscreen things, it will end up likely breaking. So a preference will allow Cordova to actually manage it in a way that you don't need to clone.

I'll re-open this issue. I'm hoping to find at least some time over the xmas break to work on some cordova stuff. I'll see if I can find a way to make the change so that it produces both the existing theme, and a v27 version of that theme which will include android:windowLayoutInDisplayCutoutMode as a configurable property. I'll default the value to never, taking the word that it reverts Cordova apps to previous behaviour as seen in cordova-android@10. As you say, we can re-evaluate the defaults later should upstream fixes the issue.

@breautek breautek reopened this Dec 15, 2022
@breautek breautek added bug and removed help wanted Requires Triage Issues that requires further analysis labels Dec 15, 2022
@breautek breautek added this to the 11.0.1 milestone Dec 15, 2022
@LorinczTimotei
Copy link

I had the same problem on Android 13 phones

but

I was able to solve the problem by deleting the plugin [email protected] and installing the plugin [email protected]

and

to be ok on android 12 or lower, I also put in .css

html.device-android .statusbar,
html.device-ios .statusbar {
height: constant(safe-area-inset-top);
height: env(safe-area-inset-top);
}
html.device-android.device-android-13 .statusbar {
height: initial;
}
html.with-statusbar.device-android .framework7-root,
html.with-statusbar.device-ios .framework7-root {
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);
}
html.with-statusbar.device-android.device-android-13 .framework7-root {
padding-top: initial;
}

I know "safe-area-inset-top" doesn't work for Android, but I set it to override for Android 12 or lower, which is set by default!

That's how it works for me, ok :)

I use cordova 11.0.0, cordova android 11.0.0 and framework7 with vue.

@DavidTalevski
Copy link

Any updates about the preference?

@breautek
Copy link
Contributor

Upstream Android WebView Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=1094366

@breautek
Copy link
Contributor

Moving this from the 11.0.1 milestone to 12.0.0 milestone because master already has the major version bump, however unsure if the workaround noted in #1465 (comment) will make it. I feel it may be better to document the quirk and provide instructions on how to implement the workaround solution instead.

@breautek breautek modified the milestones: 11.0.1, 12.0.0 Mar 20, 2023
@DavidTalevski
Copy link

DavidTalevski commented Mar 20, 2023

The current workaround I'm using is creating a hook named: "windowLayoutInDisplayCutoutMode". Here's the code for the hook:

const fs = require('fs');
const path = require("path");

module.exports = function (ctx) {
    const xml = `<?xml version='1.0' encoding='utf-8'?>
    <resources>
        <style name="Theme.App.SplashScreen" parent="Theme.SplashScreen.IconBackground">
            <item name="windowSplashScreenBackground">@color/cdv_splashscreen_background</item>
            <item name="windowSplashScreenAnimatedIcon">@drawable/ic_cdv_splashscreen</item>
            <item name="windowSplashScreenAnimationDuration">200</item>
            <item name="postSplashScreenTheme">@style/Theme.AppCompat.NoActionBar</item>
            <item name="android:windowLayoutInDisplayCutoutMode">never</item>
        </style>
    </resources>`

    const themesXML = path.join(ctx.opts.projectRoot, 'platforms/android/app/src/main/res/values/themes.xml');

    fs.writeFileSync(themesXML, xml);
};

And then I imported the hook in the projects config.xml:

<platform name="android">
    <hook type="after_platform_add" src="hooks/android/windowLayoutInDisplayCutoutMode.js" />
</platform>

The settings probably won't work for everyone, the best way would be to parse the XML and just insert the tag into it.

@AshleyScirra
Copy link

Is there any existing Cordova plugin that can work around this in the mean time? Perhaps someone who has a good workaround could publish one? I'd much appreciate it!

@breautek
Copy link
Contributor

breautek commented Apr 13, 2023

Is there any existing Cordova plugin that can work around this in the mean time? Perhaps someone who has a good workaround could publish one? I'd much appreciate it!

I just open sourced our internal package we are using @totalpave/cordova-plugin-insets. It can be installed by doing cordova plugin add @totalpave/cordova-plugin-insets. I don't have documentation written but you can add a listener for inset changes like so:

totalpave.Insets.addListener((inset) => {
   /* inset = {
        top: number;
        right: number;
        bottom: number;
        left: number;
    };
   */
})

The plugin keeps track of the inset regardless of attached listeners. So you can also poll the current known inset information via totalpave.Insets.getInsets().

The plugin provides an Android implementation only. So if you support multiple platforms, you need to guard it. Will be publishing to official NPM registry soon.

The plugin isn't perfect and currently does produce an issue on app launch where the bottom inset is higher than expected. It's also not yet used in any of our production apps.

This plugin aims just to provide the Inset information. It's up to you on how you want to consume it. For example, if you want to make this information available to CSS, then you could programmatically set the CSS variables on the listener (See W3Schools example)

Because I don't foresee a reasonable fix (I believe it is an Android Webview bug), I'll be removing this from the 12.x milestone.

EDIT:

The above example for the plugin refers to an older version, but the latest version does have docs

@ramesh0452

This comment was marked as off-topic.

@breautek

This comment was marked as off-topic.

@ramesh0452

This comment was marked as off-topic.

@AndyCavedal
Copy link

Is there anyway then to use the Capacitor plugin for safe-area, in a Cordova Ionic application? Are they compatible with each other?

@AshleyScirra
Copy link

FWIW we use cordova-plugin-android-notch to work around this - it still seems to work fine, although you have to implement safe area logic with JavaScript.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests