<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://lavernmast.com/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://lavernmast.com/blog/" rel="alternate" type="text/html" /><updated>2026-11-07T21:28:57+00:00</updated><id>https://lavernmast.com/blog/feed.xml</id><title type="html">The Slippery ELM Blog</title><subtitle>Join me for a walk through yuckiness</subtitle><entry><title type="html">Displaying MagicMirror on an Old Android Tablet</title><link href="https://lavernmast.com/blog/magicmirror-on-android-tablet/" rel="alternate" type="text/html" title="Displaying MagicMirror on an Old Android Tablet" /><published>2025-08-16T00:00:00+00:00</published><updated>2025-08-16T00:00:00+00:00</updated><id>https://lavernmast.com/blog/magicmirror-on-android-tablet</id><content type="html" xml:base="https://lavernmast.com/blog/magicmirror-on-android-tablet/"><![CDATA[<p><img src="https://lavernmast.com/assets/images/framedmirror(censored).jpg" alt="framed tablet running MagicMirror" /></p>

<p>For quite a while I have had a smart display setup by my desk to show weather, calendar events, etc. It’s basically an old laptop with linux installed on it running <a href="https://magicmirror.builders/">MagicMirror</a>. Plop a monitor on top of that and you have a very customizable smart display. But since MagicMirror also has functions as a server any device on the local network with a screen could in theory be another smart display. This is where I had the idea to use an old Android tablet to display some of the same information on the main smart display at another location in the house. It’s an old nexus 7 that I rooted and debloated but still to slow to use for most things. Then the touchscreen stopped working and so it wasn’t getting used for anything and was just collecting dust.<br />
My goal was simple in theory, but a lot trickier in practice:</p>

<ol>
  <li>Display my MagicMirror webpage (<code class="language-plaintext highlighter-rouge">http://192.168.x.x:8080</code>) in <strong>fullscreen</strong>.</li>
  <li>Hide <strong>all browser UI</strong> (no address bar, no buttons).</li>
  <li>Keep it running 24/7 like a real wall-mounted smart display.</li>
</ol>

<p>Here are the things I tried and mistakes I made until I finally got it working using <strong>GeckoView</strong>.</p>

<hr />
<h2 id="various-3rd-party-kiosk-apps">Various 3rd Party Kiosk Apps</h2>

<p>There are tons of 3rd party kiosk apps available for Android, such as <strong>Full Kiosk Browser</strong>, <strong>Surefox</strong>, and <strong>Fullscreen Browser</strong>. I gave them a try but all of them failed to properly load MagicMirror. I later found out this was probably because they were using android WebView.</p>

<hr />
<h2 id="attempt-1--basic-webview-app">Attempt #1 — Basic WebView App</h2>

<p>I thought it would be easy enough just to make a custom android app that basically just opened my MagicMirror webpage in webview. With the help of ChatGPT I spun one up in no time.
I started with Android Studio, an Empty Activity, and a WebView:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">webView</span><span class="o">.</span><span class="na">getSettings</span><span class="o">().</span><span class="na">setJavaScriptEnabled</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="n">webView</span><span class="o">.</span><span class="na">setWebViewClient</span><span class="o">(</span><span class="k">new</span> <span class="nc">WebViewClient</span><span class="o">());</span>
<span class="n">webView</span><span class="o">.</span><span class="na">loadUrl</span><span class="o">(</span><span class="s">"http://192.168.x.x:8080"</span><span class="o">);</span>
</code></pre></div></div>

<p><strong>Result:</strong> The page wouldn’t fully load which I discovered was because the version of webview in Android 5.1 was too old to support JavaScript ES6 which is necessary for MagicMirror. I looked into updating webview but it soon became clear that that was no easy task if possible at all.</p>

<hr />
<h2 id="attempt-2--launch-chrome-in-kiosk-mode">Attempt #2 — Launch Chrome in Kiosk Mode</h2>

<p>Since webview wasn’t a viable option why not just use Chrome but launch it in kiosk mode. Brilliant, right? No. <br />
I tried launching Chrome via ADB with kiosk flags:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell am start <span class="nt">-a</span> android.intent.action.VIEW <span class="se">\</span>
<span class="nt">-d</span> <span class="s2">"http://192.168.x.x:8080"</span> <span class="se">\</span>
com.android.chrome <span class="se">\</span>
<span class="nt">--ez</span> fullscreen <span class="nb">true</span> <span class="se">\</span>
<span class="nt">--ez</span> kiosk <span class="nb">true</span>
</code></pre></div></div>

<p><strong>Result:</strong> Chrome opened, but <strong>still had the address bar</strong>.<br />
Turns out Chrome for Android ignores desktop-style kiosk flags.</p>

<hr />
<h2 id="attempt-3--crosswalk-web-runtime">Attempt #3 — Crosswalk Web Runtime</h2>

<p>Next, I looked at <strong>Crosswalk</strong>, which bundles Chromium in your APK.<br />
It’s perfect for old Androids… <strong>except</strong> Crosswalk was discontinued years ago.</p>

<p>I set up my <code class="language-plaintext highlighter-rouge">build.gradle.kts</code> like this:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">repositories</span> <span class="p">{</span>
    <span class="nf">maven</span> <span class="p">{</span>
        <span class="n">url</span> <span class="p">=</span> <span class="nf">uri</span><span class="p">(</span><span class="s">"http://download.01.org/crosswalk/releases/crosswalk/android/maven2"</span><span class="p">)</span>
        <span class="n">isAllowInsecureProtocol</span> <span class="p">=</span> <span class="k">true</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nf">dependencies</span> <span class="p">{</span>
    <span class="nf">implementation</span><span class="p">(</span><span class="s">"org.xwalk:xwalk_core_library:23.53.589.4"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>But the repo was flaky, and many versions were missing.<br />
I tried hosting the <code class="language-plaintext highlighter-rouge">.aar</code> locally with:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">repositories</span> <span class="p">{</span>
    <span class="nf">flatDir</span> <span class="p">{</span> <span class="nf">dirs</span><span class="p">(</span><span class="s">"libs"</span><span class="p">)</span> <span class="p">}</span>
<span class="p">}</span>
<span class="nf">dependencies</span> <span class="p">{</span>
    <span class="nf">implementation</span><span class="p">(</span><span class="n">name</span> <span class="p">=</span> <span class="s">"xwalk_core_library-23.53.589.0"</span><span class="p">,</span> <span class="n">ext</span> <span class="p">=</span> <span class="s">"aar"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Result:</strong> All the links to Crosswalk’s repositories were broken. In theory Crosswalk <em>could</em> work if you find a working <code class="language-plaintext highlighter-rouge">.aar</code>, but it’s stuck on Chromium ~53, which is still outdated for modern MagicMirror modules.</p>

<hr />

<h2 id="attempt-4--enter-geckoview-final-solution">Attempt #4 — Enter GeckoView (Final Solution)</h2>

<p>With Chromium-based solutions hitting brick walls, I switched gears to Mozilla’s GeckoView
<strong>GeckoView</strong> is basically Firefox as an embeddable view.<br />
It supports modern web standards, is actively maintained, and works on old Android versions.</p>

<h3 id="1-create-an-empty-activity-project">1. Create an Empty Activity Project</h3>

<p>In Android Studio:</p>

<ol>
  <li><strong>File → New → New Project</strong></li>
  <li>Choose <strong>Empty Views Activity</strong></li>
  <li>Name it <code class="language-plaintext highlighter-rouge">GeckoMM</code></li>
</ol>

<h3 id="2-add-geckoview-dependency">2. Add GeckoView Dependency</h3>

<p>In <code class="language-plaintext highlighter-rouge">settings.gradle</code>:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">dependencyResolutionManagement</span> <span class="p">{</span>
    <span class="n">repositoriesMode</span><span class="p">.</span><span class="k">set</span><span class="p">(</span><span class="nc">RepositoriesMode</span><span class="p">.</span><span class="nc">PREFER_SETTINGS</span><span class="p">)</span>
    <span class="nf">repositories</span> <span class="p">{</span>
        <span class="nf">google</span><span class="p">()</span>
        <span class="nf">mavenCentral</span><span class="p">()</span>
        <span class="nf">maven</span> <span class="p">{</span> <span class="n">url</span> <span class="err">'</span><span class="n">https</span><span class="p">:</span><span class="c1">//maven.mozilla.org/maven2/' }</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In <code class="language-plaintext highlighter-rouge">app/build.gradle</code>:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">dependencies</span> <span class="p">{</span>
    <span class="nf">implementation</span><span class="p">(</span><span class="s">"org.mozilla.geckoview:geckoview:115.0.20230717105536"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><em>(Use the latest GeckoView version supported by your Android Studio.)</em></p>

<h3 id="3-update-androidmanifestxml">3. Update <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code></h3>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;uses-permission</span> <span class="na">android:name=</span><span class="s">"android.permission.RECEIVE_BOOT_COMPLETED"</span> <span class="nt">/&gt;</span>

<span class="nt">&lt;application</span>
    <span class="na">android:allowBackup=</span><span class="s">"true"</span>
    <span class="na">android:dataExtractionRules=</span><span class="s">"@xml/data_extraction_rules"</span>
    <span class="na">android:fullBackupContent=</span><span class="s">"@xml/backup_rules"</span>
    <span class="na">android:icon=</span><span class="s">"@mipmap/ic_launcher"</span>
    <span class="na">android:label=</span><span class="s">"@string/app_name"</span>
    <span class="na">android:roundIcon=</span><span class="s">"@mipmap/ic_launcher_round"</span>
    <span class="na">android:supportsRtl=</span><span class="s">"true"</span>
    <span class="na">android:theme=</span><span class="s">"@android:style/Theme.NoTitleBar.Fullscreen"</span>
    <span class="na">tools:targetApi=</span><span class="s">"31"</span><span class="nt">&gt;</span>

    <span class="nt">&lt;receiver</span> <span class="na">android:name=</span><span class="s">".BootReceiver"</span> <span class="na">android:enabled=</span><span class="s">"true"</span> <span class="na">android:exported=</span><span class="s">"true"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;intent-filter&gt;</span>
            <span class="nt">&lt;action</span> <span class="na">android:name=</span><span class="s">"android.intent.action.BOOT_COMPLETED"</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;/intent-filter&gt;</span>
    <span class="nt">&lt;/receiver&gt;</span>

    <span class="nt">&lt;activity</span>
        <span class="na">android:name=</span><span class="s">".MainActivity"</span>
        <span class="na">android:exported=</span><span class="s">"true"</span>
        <span class="na">android:label=</span><span class="s">"@string/app_name"</span>
        <span class="na">android:theme=</span><span class="s">"@style/Theme.GeckoMM"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;intent-filter&gt;</span>
            <span class="nt">&lt;action</span> <span class="na">android:name=</span><span class="s">"android.intent.action.MAIN"</span> <span class="nt">/&gt;</span>

            <span class="nt">&lt;category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.LAUNCHER"</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;/intent-filter&gt;</span>

    <span class="nt">&lt;/activity&gt;</span>
<span class="nt">&lt;/application&gt;</span>
</code></pre></div></div>

<h3 id="4-mainactivitykt">4. <code class="language-plaintext highlighter-rouge">MainActivity.kt</code></h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="nn">com.example.geckomm</span>

<span class="k">import</span> <span class="nn">android.app.Activity</span>
<span class="k">import</span> <span class="nn">android.os.Bundle</span>
<span class="k">import</span> <span class="nn">android.view.View</span>
<span class="k">import</span> <span class="nn">org.mozilla.geckoview.GeckoRuntime</span>
<span class="k">import</span> <span class="nn">org.mozilla.geckoview.GeckoSession</span>
<span class="k">import</span> <span class="nn">org.mozilla.geckoview.GeckoView</span>
<span class="k">import</span> <span class="nn">android.content.BroadcastReceiver</span>
<span class="k">import</span> <span class="nn">android.content.Context</span>
<span class="k">import</span> <span class="nn">android.content.Intent</span>

<span class="kd">class</span> <span class="nc">BootReceiver</span> <span class="p">:</span> <span class="nc">BroadcastReceiver</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onReceive</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">,</span> <span class="n">intent</span><span class="p">:</span> <span class="nc">Intent</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">intent</span><span class="p">.</span><span class="n">action</span> <span class="p">==</span> <span class="nc">Intent</span><span class="p">.</span><span class="nc">ACTION_BOOT_COMPLETED</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">val</span> <span class="py">i</span> <span class="p">=</span> <span class="nc">Intent</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="nc">MainActivity</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">)</span>
            <span class="n">i</span><span class="p">.</span><span class="nf">addFlags</span><span class="p">(</span><span class="nc">Intent</span><span class="p">.</span><span class="nc">FLAG_ACTIVITY_NEW_TASK</span><span class="p">)</span>
            <span class="n">context</span><span class="p">.</span><span class="nf">startActivity</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">Activity</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">lateinit</span> <span class="kd">var</span> <span class="py">geckoView</span><span class="p">:</span> <span class="nc">GeckoView</span>
    <span class="k">private</span> <span class="k">lateinit</span> <span class="kd">var</span> <span class="py">geckoSession</span><span class="p">:</span> <span class="nc">GeckoSession</span>
    <span class="k">private</span> <span class="k">lateinit</span> <span class="kd">var</span> <span class="py">geckoRuntime</span><span class="p">:</span> <span class="nc">GeckoRuntime</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span>
        <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span>
        <span class="n">window</span><span class="p">.</span><span class="nf">addFlags</span><span class="p">(</span><span class="n">android</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="nc">WindowManager</span><span class="p">.</span><span class="nc">LayoutParams</span><span class="p">.</span><span class="nc">FLAG_KEEP_SCREEN_ON</span><span class="p">)</span>

        <span class="c1">// Fullscreen setup</span>
        <span class="n">window</span><span class="p">.</span><span class="n">decorView</span><span class="p">.</span><span class="n">systemUiVisibility</span> <span class="p">=</span> <span class="p">(</span>
                <span class="nc">View</span><span class="p">.</span><span class="nc">SYSTEM_UI_FLAG_FULLSCREEN</span> <span class="n">or</span>
                        <span class="nc">View</span><span class="p">.</span><span class="nc">SYSTEM_UI_FLAG_HIDE_NAVIGATION</span> <span class="n">or</span>
                        <span class="nc">View</span><span class="p">.</span><span class="nc">SYSTEM_UI_FLAG_IMMERSIVE_STICKY</span>
                <span class="p">)</span>
        <span class="n">actionBar</span><span class="o">?.</span><span class="nf">hide</span><span class="p">()</span>

        <span class="c1">// Create GeckoView and load it as content</span>
        <span class="n">geckoView</span> <span class="p">=</span> <span class="nc">GeckoView</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
        <span class="nf">setContentView</span><span class="p">(</span><span class="n">geckoView</span><span class="p">)</span>

        <span class="c1">// Setup GeckoSession and Runtime</span>
        <span class="n">geckoRuntime</span> <span class="p">=</span> <span class="nc">GeckoRuntime</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
        <span class="n">geckoSession</span> <span class="p">=</span> <span class="nc">GeckoSession</span><span class="p">()</span>
        <span class="n">geckoSession</span><span class="p">.</span><span class="k">open</span><span class="p">(</span><span class="n">geckoRuntime</span><span class="p">)</span>
        <span class="n">geckoView</span><span class="p">.</span><span class="nf">setSession</span><span class="p">(</span><span class="n">geckoSession</span><span class="p">)</span>

        <span class="c1">// Load your local MagicMirror URL</span>
        <span class="n">geckoSession</span><span class="p">.</span><span class="nf">loadUri</span><span class="p">(</span><span class="s">"http://192.168.x.x:8080"</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="step-6-included-features">Step 6: Included features</h2>

<p>Once GeckoView was working, I added:</p>

<ol>
  <li><strong>Autostart on reboot</strong> (via a BootReceiver).</li>
  <li><strong>Keep screen on</strong> with:</li>
</ol>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">window</span><span class="p">.</span><span class="nf">addFlags</span><span class="p">(</span><span class="n">android</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="nc">WindowManager</span><span class="p">.</span><span class="nc">LayoutParams</span><span class="p">.</span><span class="nc">FLAG_KEEP_SCREEN_ON</span><span class="p">)</span>
</code></pre></div></div>
<p>Because of the poor battery of the tablet I will probably add a feature to turn off the display at night and turn it back on in the morning.</p>
<ol>
  <li><strong>Night power saving</strong> (turn screen off at 22:30, back on at 6:30 via root <code class="language-plaintext highlighter-rouge">input keyevent</code> commands in a scheduled script).</li>
</ol>

<hr />
<h2 id="edit-magicmirror-css">Edit MagicMirror CSS</h2>
<p>Because the tablet screen is a lot smaller than the main smart display, I had to find a way to remove some of the modules and reduce the font size on the tablet’s display without affecting the main display. Unfortunately MagicMirror doesn’t really have a built in way to do this since it isn’t really meant for running multiple displays off of one server. So I had to edit the <code class="language-plaintext highlighter-rouge">custom.css</code> file in MagicMirror. <br />
I used a media query so that the css would only apply to the tablet.</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">@media</span> <span class="p">(</span><span class="n">max-width</span><span class="p">:</span> <span class="m">1000px</span><span class="p">)</span> <span class="p">{</span>
    <span class="nc">.module.MMM-Flights</span> <span class="p">{</span>
        <span class="nl">display</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nc">.module.MMM-MotionDetector</span> <span class="p">{</span>
        <span class="nl">display</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nc">.module.newsfeed</span> <span class="p">{</span>
        <span class="nl">display</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nc">.module.MMM-Dad-Jokes</span> <span class="p">{</span>
        <span class="nl">display</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nc">.xsmall</span> <span class="p">{</span>
      <span class="nl">font-size</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nc">.small</span> <span class="p">{</span>
      <span class="nl">font-size</span><span class="p">:</span> <span class="m">25px</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nc">.medium</span> <span class="p">{</span>
      <span class="nl">font-size</span><span class="p">:</span> <span class="m">35px</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nc">.large</span> <span class="p">{</span>
      <span class="nl">font-size</span><span class="p">:</span> <span class="m">70px</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nc">.xlarge</span> <span class="p">{</span>
      <span class="nl">font-size</span><span class="p">:</span> <span class="m">80px</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />
<h2 id="conclusion">Conclusion</h2>

<p>After hours of trial and error with outdated WebViews, Chrome kiosk hacks, and Crosswalk dead-ends, <strong>GeckoView</strong> turned out to be the perfect solution.</p>

<p>Now my old Android tablet:</p>

<ol>
  <li>Auto-launches GeckoMM when the system boots up.</li>
  <li>Shows it fullscreen with no UI</li>
  <li>Handles all modules flawlessly</li>
</ol>

<p>If you’ve got an old tablet lying around, this method can turn it into a smart display without buying new hardware.</p>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[]]></summary></entry></feed>