Search code examples
androidchromecast

Why is Android TV app having a Web View with low resolution


I've a dashboard website (Grafana) that I would like to present on my 4K TV in the office using a Chromecast with Google TV (4K). I'm trying to create a very tiny Android TV app (minimum SDK: API 31) with a embedded web browser displaying this static dashboard. The application is installed on the Chromecast by uploading the APK to my Google Drive and installing it using TV File Commander.

When I start the application on my Chromecast the web view is running in low resolution. I've changed the app to visit this website instead -- and it seems that the browser is only running in 540x960 pixels: https://www.webfx.com/tools/whats-my-browser-size/

How can I fix this?

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_browse_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    tools:deviceIds="tv"
    tools:ignore="MergeRootFrame" >
    <WebView
            android:layout_width="match_parent"
            android:layout_height="match_parent" android:id="@+id/webView"/>
</FrameLayout>

MainActivity

package com.company.browser

import android.os.Bundle
import android.webkit.WebSettings
import android.webkit.WebView
import androidx.fragment.app.FragmentActivity

class MainActivity : FragmentActivity() {

    private lateinit var webView: WebView

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

        webView = findViewById(R.id.webView)
        val webSettings: WebSettings = webView.settings
        webSettings.domStorageEnabled = true
        webSettings.javaScriptEnabled = true

        webView.loadUrl("https://www.webfx.com/tools/whats-my-browser-size/")
    }
}

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" >

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

    <uses-feature
        android:name="android.hardware.touchscreen"
        android:required="false" />
    <uses-feature
        android:name="android.software.leanback"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.Browser" >
        <activity
            android:name=".MainActivity"
            android:banner="@drawable/app_icon_your_company"
            android:exported="true"
            android:icon="@drawable/app_icon_your_company"
            android:label="@string/app_name"
            android:logo="@drawable/app_icon_your_company"
            android:screenOrientation="landscape" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

Solution

  • WebViews on Android TVs are quite frustrating (at least for me). Hope this helps you to understand the root cause and the approaches to address it.

    Where does the 540x960 resolution come from?

    The WebFx browser size tool shows you the CSS pixels. Those are logical and mapped onto physical screen pixels in a certain way to take dpi into account. Most of Android TV dongles I've seen have window.devicePixelRatio == 4. You can easily check it if you have access to JS console.

    It means that it's 4 physical pixels per logical one.

    4K is 3840x2160

    4K / 4 is exactly 540x960

    Where does devicePixelRatio come from and why 4?

    devicePixelRatio for WebView is calculated the same way as density independent pixels in android UI.

    android density buckets

    1:1 density is 160, other density buckets are multiples or fractions of it.

    You can check your device's density using the following adb command:

    $ adb shell wm density
    Physical density: 640
    

    The value above (640) is from my Chromecast. Now following the guidance on the image above it's 640 = 160 * 4 or 4x the 1:1 density. WebView backed by chrome does this math and sets window.devicePixelRatio to 4. That code is not accessible unless you're into patching and building chromium from scratch.

    The density is the same (640) on Android 4K TV emulator, by the way. You can use it for debugging and experiments.

    Where does density of 640 come from? It's set by device developers when they build the device's firmware.

    How to deal with all of these density issues?

    These are the three basic options that come into my mind, but it's not an exhaustive list, just to push you to the right direction.

    Adjust the web page

    You can fully adjust CSS of your page to take screen density into account using -webkit-device-pixel-ratio

    #header {
        /* dimensions */
    }
    
    @media screen and (-webkit-device-pixel-ratio: 1.5) {
        /* CSS for high-density screens */
        #header {
            /* overridden dimensions or scale value etc. */
        }
    }
    

    More details here: https://developer.android.com/develop/ui/views/layout/webapps/targeting#DensityCSS

    Adjust device's density setting

    It will affect the UI of every app and increase the load on GPU, but still a valid option.

    $ adb shell wm density ${YOUR_DENSITY_VALUE}
    

    Hack and inject page scaling into the loaded page

    Very hacky, but works.

    webView.webViewClient = object : WebViewClient() {
        override fun onPageFinished(view: WebView?, url: String?) {
            view?.loadUrl("javascript:document.body.style.zoom = 1/window.devicePixelRatio;")
            super.onPageFinished(view, url)
        }
    }