Android WebView full screen video exit create extra blank white space

Issue

I have an android webapp where I want to play videos in fullscreen mode with system bar and navigation bar hidden. And everything works fine without any issue.

The original view of my MainActivity is like below shown in the picture.

enter image description here

But the problem occurs when I exit from the full screen mode. Whenever I exit from full screen videos, few blank white spaces are created.

I have tried with YouTube, Vimeo and Dailymotion. But the problem remains for all of these sites. Below I have given their demo, how they look like after exiting from full screen mode.

YouTube:

enter image description here

Vimeo:

enter image description here

DailyMotion:

enter image description here

Here is my activity_main.xml

<?xml version"1.0" encoding"utf-8"?>
<RelativeLayout 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"
    android:fitsSystemWindows"true"
    tools:context".MainActivity">

    <ProgressBar
        android:id"@+id/mainProgressBar"
        style"?android:attr/progressBarStyleHorizontal"
        android:layout_width"match_parent"
        android:layout_height"wrap_content"
        android:layout_alignParentStart"true"
        android:layout_alignParentTop"true"
        android:layout_alignParentEnd"true"
        android:max"100"
        android:visibility"gone" />

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id"@+id/mainSrl"
        android:layout_width"match_parent"
        android:layout_height"match_parent"
        android:layout_above"@id/mainControllerContainer"
        android:layout_below"@id/mainProgressBar">

        <com.jobayr.webapp.ObservableWebView
            android:id"@+id/mainWebView"
            android:layout_width"match_parent"
            android:layout_height"match_parent" />

    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

    <LinearLayout
        android:id"@+id/mainControllerContainer"
        android:layout_width"match_parent"
        android:layout_height"?actionBarSize"
        android:layout_alignParentBottom"true"
        android:layout_centerHorizontal"true"
        android:background"@color/colorPrimary"
        android:gravity"center"
        android:orientation"horizontal"
        android:weightSum"4">

        <androidx.appcompat.widget.AppCompatImageView
            android:id"@+id/mainControllerBackBtn"
            android:layout_width"0dp"
            android:layout_height"match_parent"
            android:layout_weight"1"
            android:background"?selectableItemBackgroundBorderless"
            android:clickable"true"
            android:focusable"true"
            android:padding"12dp"
            app:srcCompat"@drawable/icon_back" />

        <androidx.appcompat.widget.AppCompatImageView
            android:id"@+id/mainControllerRefreshBtn"
            android:layout_width"0dp"
            android:layout_height"match_parent"
            android:layout_weight"1"
            android:background"?selectableItemBackgroundBorderless"
            android:clickable"true"
            android:focusable"true"
            android:padding"12dp"
            app:srcCompat"@drawable/icon_refresh" />

        <androidx.appcompat.widget.AppCompatImageView
            android:id"@+id/mainControllerHomeBtn"
            android:layout_width"0dp"
            android:layout_height"match_parent"
            android:layout_weight"1"
            android:background"?selectableItemBackgroundBorderless"
            android:clickable"true"
            android:focusable"true"
            android:padding"12dp"
            app:srcCompat"@drawable/icon_home" />

        <androidx.appcompat.widget.AppCompatImageView
            android:id"@+id/mainControllerForwardBtn"
            android:layout_width"0dp"
            android:layout_height"match_parent"
            android:layout_weight"1"
            android:background"?selectableItemBackgroundBorderless"
            android:clickable"true"
            android:focusable"true"
            android:padding"12dp"
            app:srcCompat"@drawable/icon_forward" />

    </LinearLayout>

</RelativeLayout>

MainActivity.kt

class MainActivity : AppCompatActivity(), AdvancedWebView.Listener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        init()
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.mian_menu, menu)
        return super.onCreateOptionsMenu(menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        if (item.itemId  R.id.miClearCookies) {
            AlertDialog.Builder(this)
                .setTitle("Clear Data")
                .setMessage("Are you sure you want to delete browser data?")
                .setPositiveButton("Delete") { _, _ ->
                    WebStorage.getInstance().deleteAllData()
                    CookieManager.getInstance().removeAllCookies(null)
                    CookieManager.getInstance().flush()
                    mainWebView.clearCache(true)
                    mainWebView.clearFormData()
                    mainWebView.clearHistory()
                    mainWebView.clearSslPreferences()
                }
                .setNegativeButton("Cancel", null)
                .show()
        } else if (item.itemId  R.id.miExit) {
            AlertDialog.Builder(this)
                .setTitle("Confirmation")
                .setMessage("Are you sure you want to exit?")
                .setPositiveButton("YES") { _, _ ->
                    finishAffinity()
                }
                .setNegativeButton("CANCEL", null)
                .show()
        }
        return super.onOptionsItemSelected(item)
    }

    override fun onResume() {
        super.onResume()
        mainWebView.onResume()
    }

    override fun onPause() {
        super.onPause()
        mainWebView.onPause()
    }

    override fun onDestroy() {
        super.onDestroy()
        mainWebView.onDestroy()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        mainWebView.onActivityResult(requestCode, resultCode, data)
    }

    private fun init() {
        goOn()
        initCallback()
    }

    private fun goOn() {
        val permissionListener  object : PermissionListener {

            override fun onPermissionGranted() {
                initWebView()
            }

            override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
                AlertDialog.Builder(this@MainActivity)
                    .setTitle("Permission Required")
                    .setMessage("Kindly grant all the permission to use this app")
                    .setPositiveButton("Retry") { _, _ ->
                        goOn()
                    }
                    .setNegativeButton("Cancel") { _, _ ->
                        finishAffinity()
                    }
                    .show()
            }
        }

        TedPermission.with(this)
            .setPermissionListener(permissionListener)
            .setDeniedMessage("You need to grant all the permission to use this app")
            .setPermissions(
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_EXTERNAL_STORAGE
            )
            .check()
    }

    @SuppressLint("SetJavaScriptEnabled")
    private fun initWebView() {
        mainWebView.loadUrl(getString(R.string.url), true)
        mainWebView.settings.javaScriptEnabled  true
        mainWebView.setListener(this, this)
        mainWebView.settings.javaScriptCanOpenWindowsAutomatically  true
        mainWebView.webViewClient  WebViewClient()
        mainWebView.settings.setSupportZoom(false)
        mainWebView.settings.displayZoomControls  false
        mainWebView.setCookiesEnabled(true)
        mainWebView.setThirdPartyCookiesEnabled(true)
        mainWebView.setMixedContentAllowed(true)
        mainWebView.webChromeClient  CustomChromeClient(this, mainProgressBar)
    }

    private fun initCallback() {
        mainControllerBackBtn.setOnClickListener {
            if (mainWebView.canGoBack()) {
                mainWebView.goBack()
            } else Toast.makeText(this, "Going Back Is Not Available", Toast.LENGTH_SHORT)
                .show()
        }
        mainControllerRefreshBtn.setOnClickListener {
            mainWebView.reload()
        }
        mainControllerHomeBtn.setOnClickListener {
            mainWebView.loadUrl(getString(R.string.url), true)
        }
        mainControllerForwardBtn.setOnClickListener {
            if (mainWebView.canGoForward()) {
                mainWebView.goForward()
            } else Toast.makeText(this, "Going Forward Is Not Available", Toast.LENGTH_SHORT)
                .show()
        }
        mainSrl.setOnRefreshListener {
            mainWebView.reload()
        }
        mainWebView.setOnScrollChangedCallback { _, t, _, oldt ->
            if (t > oldt) {
                if (t - oldt > 60) {
                    supportActionBar?.hide()
                    mainControllerContainer.visibility  View.GONE
                }
            } else if (t < oldt) {
                if (oldt - t > 60) {
                    supportActionBar?.show()
                    mainControllerContainer.visibility  View.VISIBLE
                }
            }
        }
    }

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (event!!.action  KeyEvent.ACTION_DOWN) {
            when (keyCode) {
                KeyEvent.KEYCODE_BACK -> {
                    if (mainWebView.canGoBack()) {
                        mainWebView.goBack()
                    } else {
                        AlertDialog.Builder(this)
                            .setTitle("Confirmation")
                            .setMessage("Are you sure you want to exit?")
                            .setPositiveButton("YES") { _, _ ->
                                finishAffinity()
                            }
                            .setNegativeButton("CANCEL", null)
                            .show()
                    }
                    return true
                }
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    override fun onPageFinished(url: String?) {
        if (mainSrl.isRefreshing) {
            mainSrl.isRefreshing  false
        }
    }

    override fun onPageError(errorCode: Int, description: String?, failingUrl: String?) {
        if (mainSrl.isRefreshing) {
            mainSrl.isRefreshing  false
        }
        description?.let {
            Toast.makeText(this, it, Toast.LENGTH_LONG).show()
        }
    }

    override fun onDownloadRequested(
        url: String?,
        suggestedFilename: String?,
        mimeType: String?,
        contentLength: Long,
        contentDisposition: String?,
        userAgent: String?
    ) {
        try {
            if (AdvancedWebView.handleDownload(this, url, suggestedFilename)) {
                Toast.makeText(this, "Download Started", Toast.LENGTH_LONG).show()
            } else {
                AlertDialog.Builder(this)
                    .setTitle("Download Error")
                    .setMessage("Couldn't download ${suggestedFilename}. Make sure your download manager is working properly.")
                    .setPositiveButton("OK", null)
                    .show()
            }
        } catch (e: Exception) {
            AlertDialog.Builder(this)
                .setTitle("Download Error")
                .setMessage(e.message.toString())
                .setPositiveButton("OK", null)
                .show()
        }
    }

    override fun onExternalPageRequest(url: String?) {
        mainWebView.loadUrl(url, true)
    }

    override fun onPageStarted(url: String?, favicon: Bitmap?) {}

}

CustomChromeClient.java

public class CustomChromeClient extends WebChromeClient {

    private Activity activity;
    private ProgressBar progressBar;
    private View mCustomView;
    private WebChromeClient.CustomViewCallback mCustomViewCallback;
    private int mOriginalOrientation;
    private int mOriginalSystemUiVisibility;

    CustomChromeClient(Activity activity, ProgressBar progressBar) {
        this.activity  activity;
        this.progressBar  progressBar;
    }

    public Bitmap getDefaultVideoPoster() {
        if (mCustomView  null) {
            return null;
        }
        return BitmapFactory.decodeResource(activity.getResources(), 2130837573);
    }

    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        super.onProgressChanged(view, newProgress);
        if (newProgress < 100) {
            progressBar.setVisibility(View.VISIBLE);
            progressBar.setProgress(newProgress);
        } else {
            progressBar.setVisibility(View.GONE);
            progressBar.setProgress(0);
        }
    }

    public void onHideCustomView() {
        this.mCustomViewCallback.onCustomViewHidden();
        this.mCustomViewCallback  null;
        ((FrameLayout) activity.getWindow().getDecorView()).removeView(this.mCustomView);
        this.mCustomView  null;
        activity.getWindow().getDecorView().setSystemUiVisibility(this.mOriginalSystemUiVisibility);
        activity.setRequestedOrientation(this.mOriginalOrientation);
        activity.getWindow().getDecorView().invalidate();
    }

    public void onShowCustomView(View paramView, WebChromeClient.CustomViewCallback paramCustomViewCallback) {
        if (this.mCustomView ! null) {
            onHideCustomView();
            return;
        }
        this.mCustomView  paramView;
        this.mOriginalSystemUiVisibility  activity.getWindow().getDecorView().getSystemUiVisibility();
        this.mOriginalOrientation  activity.getRequestedOrientation();
        this.mCustomViewCallback  paramCustomViewCallback;
        ((FrameLayout) activity.getWindow().getDecorView()).addView(this.mCustomView, new FrameLayout.LayoutParams(-1, -1));
        activity.getWindow().getDecorView().setSystemUiVisibility(3846 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
    }
}

AndroidManifest.xml

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

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

    <application
        android:allowBackup"true"
        android:hardwareAccelerated"true"
        android:icon"@mipmap/ic_launcher"
        android:label"@string/app_name"
        android:roundIcon"@mipmap/ic_launcher_round"
        android:supportsRtl"true"
        android:theme"@style/AppTheme"
        android:usesCleartextTraffic"true"
        tools:ignore"AllowBackup,UnusedAttribute">
        <activity
            android:name".MainActivity"
            android:configChanges"orientation|keyboardHidden|screenSize|screenLayout|keyboard|layoutDirection|uiMode">
            <intent-filter>
                <action android:name"android.intent.action.MAIN" />

                <category android:name"android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

OvservableWebView.java This one is not the subject of interest (If I am not wrong. This is mainly to detect scrolling behaviour of my webview. However I am posting it also.)

public class ObservableWebView extends AdvancedWebView {

    private OnScrollChangedCallback mOnScrollChangedCallback;

    public ObservableWebView(final Context context) {
        super(context);
    }

    public ObservableWebView(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    public ObservableWebView(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onScrollChanged(final int l, final int t, final int oldl, final int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (mOnScrollChangedCallback ! null) mOnScrollChangedCallback.onScroll(l, t, oldl, oldt);
    }

    public OnScrollChangedCallback getOnScrollChangedCallback() {
        return mOnScrollChangedCallback;
    }

    public void setOnScrollChangedCallback(final OnScrollChangedCallback onScrollChangedCallback) {
        mOnScrollChangedCallback  onScrollChangedCallback;
    }

    public static interface OnScrollChangedCallback {
        public void onScroll(int l, int t, int oldl, int oldt);
    }
}

Please help me out of this problem. Any solutions, ideas will be appreciated. Thanks in advance.

Solution

I found a solution!

You have to use the classes from this repository: https://github.com/cprcrack/VideoEnabledWebView.

This was a repository created by a Stack Overflow user to help and improve playing HTML videos on fullscreen in Android WebView.

You can see it in another user answer

It was the only solution that worked for me.

Answered By – Hugo

Leave a Comment