androidwebviewandroid-webviewwebchromeclient

Android WebView full screen video exit create extra blank white space


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.