Introduction

Android app development adventures - or when an AI guy, who prefers assembly or FORTRAN over JAVA, tries to build a fullscreen (web) app displaying results of a machine learning pipeline on a device.

Notes up front. Copying the first best example code from github or stackexchange should do the job and everything is done in 15 mins, right? Hell no! Yes, on modern Android SDK versions (31+) is seems to be more straight forward. However, it using the recommended programming language Kotlin resulted in trouble with resolving the right androidx extensions via gradle and using Jetpack was a dead end as well. Hence back to JAVA it was and even that was not without troubles. This adventures is documented below. Have fun;)

Basic Setup

As we all know, the “s” in “IoT” stands for security… . Therefore, you can’t expect Android to be up to date on which software should be deployed. In this example compatibility with Android 9.0 was required.

The basic setup is straight forward. Creating a New Empty Activity with the following settings:

Name: WebViewFullscreen
Package name: com.bla.webviewfullscreen
Language: Java
Minimum SDK: API 28: Android 9.0 (Pie)

After starting an empty project, an older androidx versions needs to be specified in build.gradle (:app):

dependencies {

    implementation 'androidx.appcompat:appcompat:1.5.1'

The AndroidManifest.xml is simple and straight forward.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.WebViewFullscreen"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
    </application>

</manifest>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <WebView
        android:id="@+id/web"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:layout_editor_absoluteX="8dp"
        tools:layout_editor_absoluteY="8dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

That is pretty much all that is needed to set up the app.

Plain WebView

The simplest thing that can be done is to run the webkit.WebView activity as main activity. This is pretty straight forward. It is important to note that the “return button” should have some functionality and that links should be followed in this app without opening a new browser instance when pressing on a link. MainActivity.java looks like this:

package com.bla.webviewfullscreen;

import androidx.appcompat.app.AppCompatActivity;
import android.view.Window;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    // define variable without constructing it
    WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // hides the app's title
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // hides the app's title bar
        getSupportActionBar().hide();

        // set main activity
        setContentView(R.layout.activity_main);

        // constructs WebView (linked to the element in the layout)
        webView = findViewById(R.id.web);

        // set default url
        webView.loadUrl("https://archive-beta.ics.uci.edu/");

        // with JavaScript & zoom enabled
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setSupportZoom(true);

        // allow to use it as a web browser (aka opening links)
        // (otherwise following any other url results in opening
        // the default browser)
        webView.setWebViewClient(new WebViewClient());
    }

    // some basic navigation functionality ;)
    @Override
    public void onBackPressed()
    {
        if(webView!= null && webView.canGoBack())
            // go to previous page if such a page exists in the history
            webView.goBack();
        else
            // close app otherwise aka follow system-wide default for back button
            super.onBackPressed();
    }
}

Let’s open the app in the emulator. That looks like it should.

WebView app no fullscreen


Set to Fullscreen Mode on App Open

Next a function is needed to set the fullscreen (immersive) mode. NB! this needs to be part of class MainActivity. Immersive mode for newer versions (>= Build.VERSION_CODES.R) is super simple and straight forward. Making it work with older versions is a bit more tricky.

private void setFullScreen()
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            getWindow().setDecorFitsSystemWindows(false);
            WindowInsetsController controller = getWindow().getInsetsController();
            if(controller != null) {
                controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
                controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
            }
        }
        else {
            // for older versions
            getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
        }
    }

The full source code of the main activity looks as follows now:

package com.bla.webviewfullscreen;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Build;
import android.view.View;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    // define variable without constructing it
    WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // hides the app's title
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // hides the app's title bar
        getSupportActionBar().hide();

        // set main activity
        setContentView(R.layout.activity_main);

        // constructs WebView (linked to the element in the layout)
        webView = findViewById(R.id.web);

        // set default url
        webView.loadUrl("https://archive-beta.ics.uci.edu/");

        // with JavaScript & zoom enabled
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setSupportZoom(true);

        // allow to use it as a web browser (aka opening links)
        // (otherwise following any other url results in opening
        // the default browser)
        webView.setWebViewClient(new WebViewClient());


        // set immersive (fullscreen) mode
        setFullScreen();
    }

    // some basic navigation functionality ;)
    @Override
    public void onBackPressed()
    {
        if(webView!= null && webView.canGoBack())
            // go to previous page if such a page exists in the history
            webView.goBack();
        else
            // close app otherwise aka follow system-wide default for back button
            super.onBackPressed();
    }



    private void setFullScreen()
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            getWindow().setDecorFitsSystemWindows(false);
            WindowInsetsController controller = getWindow().getInsetsController();
            if(controller != null) {
                controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
                controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
            }
        }
        else {
            // for older versions
            getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
        }
    }
}

So, let’s see what happens this new setFullScreen function is called. On new versions everything is fine! On older versions we get this result when opening the app.

WebView app fullscreen after app start


That looks good but as soon as there is any touch interaction, this happens:

WebView app no fullscreen after touch interaction


Keep Fullscreen During Usage

Apparently, the View.SYSTEM_UI_FLAG_IMMERSIVE was missing.

package com.bla.webviewfullscreen;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Build;
import android.view.View;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    // define variable without constructing it
    WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // hides the app's title
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // hides the app's title bar
        getSupportActionBar().hide();

        // set main activity
        setContentView(R.layout.activity_main);

        // constructs WebView (linked to the element in the layout)
        webView = findViewById(R.id.web);

        // set default url
        webView.loadUrl("https://archive-beta.ics.uci.edu/");

        // with JavaScript & zoom enabled
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setSupportZoom(true);

        // allow to use it as a web browser (aka opening links)
        // (otherwise following any other url results in opening
        // the default browser)
        webView.setWebViewClient(new WebViewClient());


        // set immersive (fullscreen) mode
        setFullScreen();
    }

    // some basic navigation functionality ;)
    @Override
    public void onBackPressed()
    {
        if(webView!= null && webView.canGoBack())
            // go to previous page if such a page exists in the history
            webView.goBack();
        else
            // close app otherwise aka follow system-wide default for back button
            super.onBackPressed();
    }



    private void setFullScreen()
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            getWindow().setDecorFitsSystemWindows(false);
            WindowInsetsController controller = getWindow().getInsetsController();
            if(controller != null) {
                controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
                controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
            }
        }
        else {
            // for older versions
            getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_IMMERSIVE
                            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
        }
    }
}

Everything looks great now except when pulling down the system bar or trying to access the navigation bar.

WebView app no fullscreen after system bar pull down


Keep Fullscreen After Accessing Navigation/System Bar

It turns out the View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY was missing. NB!: on some devices tested it required both flags View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY&View.SYSTEM_UI_FLAG_IMMERSIVE to return stay in fullscreen mode and not just the sticky flag.

package com.bla.webviewfullscreen;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Build;
import android.view.View;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    // define variable without constructing it
    WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // hides the app's title
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // hides the app's title bar
        getSupportActionBar().hide();

        // set main activity
        setContentView(R.layout.activity_main);

        // constructs WebView (linked to the element in the layout)
        webView = findViewById(R.id.web);

        // set default url
        webView.loadUrl("https://archive-beta.ics.uci.edu/");

        // with JavaScript & zoom enabled
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setSupportZoom(true);

        // allow to use it as a web browser (aka opening links)
        // (otherwise following any other url results in opening
        // the default browser)
        webView.setWebViewClient(new WebViewClient());


        // set immersive (fullscreen) mode
        setFullScreen();
    }

    // some basic navigation functionality ;)
    @Override
    public void onBackPressed()
    {
        if(webView!= null && webView.canGoBack())
            // go to previous page if such a page exists in the history
            webView.goBack();
        else
            // close app otherwise aka follow system-wide default for back button
            super.onBackPressed();
    }



    private void setFullScreen()
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            getWindow().setDecorFitsSystemWindows(false);
            WindowInsetsController controller = getWindow().getInsetsController();
            if(controller != null) {
                controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
                controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
            }
        }
        else {
            // for older versions
            getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_IMMERSIVE
                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
        }
    }
}

Well, it works now. The navigation bar is semi-transparent now.

WebView app fullscreen mode works properly


However, when closing and resuming the app the fullscreen mode is gone again:

WebView app shown in recent


WebView app no fullscreen after resume


Keep Fullscreen After Resume

Luckily, there exists a class function onResume. Calling setFullScreen again after resume resolves this problem as well.

@Override
public void onResume()
{
    super.onResume();
    setFullScreen();

}

The final source code for the main activity:

package com.bla.webviewfullscreen;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Build;
import android.view.View;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    // define variable without constructing it
    WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // hides the app's title
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // hides the app's title bar
        getSupportActionBar().hide();

        // set main activity
        setContentView(R.layout.activity_main);

        // constructs WebView (linked to the element in the layout)
        webView = findViewById(R.id.web);

        // set default url
        webView.loadUrl("https://archive-beta.ics.uci.edu/");

        // with JavaScript & zoom enabled
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setSupportZoom(true);

        // allow to use it as a web browser (aka opening links)
        // (otherwise following any other url results in opening
        // the default browser)
        webView.setWebViewClient(new WebViewClient());


        // set immersive (fullscreen) mode
        setFullScreen();
    }

    // some basic navigation functionality ;)
    @Override
    public void onBackPressed()
    {
        if(webView!= null && webView.canGoBack())
            // go to previous page if such a page exists in the history
            webView.goBack();
        else
            // close app otherwise aka follow system-wide default for back button
            super.onBackPressed();
    }



    private void setFullScreen()
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            getWindow().setDecorFitsSystemWindows(false);
            WindowInsetsController controller = getWindow().getInsetsController();
            if(controller != null) {
                controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
                controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
            }
        }
        else {
            // for older versions
            getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_IMMERSIVE
                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
        }
    }

    @Override
    public void onResume()
    {
        super.onResume();
        setFullScreen();

    }
}