<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[CodeDAC Dev Notes]]></title><description><![CDATA[CodeDAC Dev Notes]]></description><link>https://codedac.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6acee1307cf5760ce7a25415/20e845e4-2fa9-472f-85aa-89860970aa04.png</url><title>CodeDAC Dev Notes</title><link>https://codedac.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 20 Oct 2026 12:57:55 GMT</lastBuildDate><atom:link href="https://codedac.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a reading ruler that floats over every Android app: touch pass-through, Android 12's opacity cap, and masks]]></title><description><![CDATA[I make ReadFocus+, a small Android app that floats a translucent strip (a "reading ruler") over the screen, mainly for people with dyslexia or ADHD who…]]></description><link>https://codedac.hashnode.dev/building-a-reading-ruler-that-floats-over-every-android-app-touch-pass-through-android-12-s-opacity-cap-and-masks</link><guid isPermaLink="true">https://codedac.hashnode.dev/building-a-reading-ruler-that-floats-over-every-android-app-touch-pass-through-android-12-s-opacity-cap-and-masks</guid><category><![CDATA[Android]]></category><category><![CDATA[Java]]></category><category><![CDATA[Accessibility]]></category><dc:creator><![CDATA[CodeDAC]]></dc:creator><pubDate>Thu, 15 Oct 2026 00:27:36 GMT</pubDate><content:encoded><![CDATA[<p>I make ReadFocus+, a small Android app that floats a translucent strip (a "reading ruler") over the screen, mainly for people with dyslexia or ADHD who lose their place in long text. It has to work on top of <strong>any</strong> app: ebooks, PDFs, browsers, news apps. So it is built on a <code>SYSTEM_ALERT_WINDOW</code> overlay. These are the parts that took the most time.</p>
<h2>Layout: one strip, two masks, one control panel</h2>
<ul>
<li><strong>The strip</strong>: one window, <code>MATCH_PARENT</code> wide with adjustable height. <code>TYPE_APPLICATION_OVERLAY</code> (<code>TYPE_PHONE</code> below API 26), <code>PixelFormat.TRANSLUCENT</code>.</li>
<li><strong>Line highlight mode</strong>: a View inside the strip painted with a <code>GradientDrawable</code> in the theme colour; the View's alpha sets the strength. No custom <code>onDraw</code>.</li>
<li><strong>Reading mask mode</strong>: two <strong>separate windows</strong>, a solid View pinned to <code>Gravity.TOP</code> and one to <code>Gravity.BOTTOM</code>, with the gap between them as the reading window. When you drag the strip, both masks follow.</li>
<li><strong>Control panel</strong>: another window holding the mode switch and the lock button.</li>
</ul>
<h2>Letting touches through to the app underneath</h2>
<p>(The code below is simplified for the article.)</p>
<p>A reading ruler is only useful if you can keep scrolling the app under it.</p>
<pre><code class="language-java">int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
          | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
          | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
if (locked) flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; // locked = touches pass through
</code></pre>
<ul>
<li>The default is <strong>locked (touch pass-through)</strong>. To move the strip you unlock it from the panel, then drag the strip itself with <code>ACTION_DOWN/MOVE/UP</code> and <code>updateViewLayout</code> on <code>params.y</code>.</li>
<li>When toggling <code>FLAG_NOT_TOUCHABLE</code>, I <strong>remove the window and add it again</strong> rather than only updating the flags.</li>
<li><code>y</code> is clamped between the status bar and the navigation bar, using <code>WindowInsets</code> (statusBars, navigationBars, systemGestures, displayCutout).</li>
</ul>
<h2>Android 12: the opacity cap on pass-through windows</h2>
<p>From Android 12 (API 31), an overlay that lets touches pass through to another app has a <strong>maximum opacity</strong>. Go above it and the system blocks the touches instead (the "untrusted touches" protection).</p>
<pre><code class="language-java">float maxAlpha = 1f;
if (Build.VERSION.SDK_INT &gt;= 31) {
    InputManager im = getSystemService(InputManager.class);
    maxAlpha = (im != null) ? im.getMaximumObscuringOpacityForTouch() : 0.8f;
}
params.alpha = maxAlpha;
</code></pre>
<p>So the window alpha is pinned to that ceiling, and the visible strength comes from the inner View's alpha and the theme colour. This is also why the mask can never be fully black.</p>
<h2>Display cutouts</h2>
<p>On phones with a notch, the top mask must not leave a gap above it:</p>
<ul>
<li>API 30+: <code>LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS</code></li>
<li>API 28–29: <code>LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES</code></li>
</ul>
<p>On rotation, <code>onConfigurationChanged</code> re-clamps the strip and redraws the masks. Every window also has <code>FLAG_KEEP_SCREEN_ON</code>, so the screen doesn't go dark mid-page.</p>
<h2>Staying alive and coming back after process death</h2>
<ul>
<li>A foreground service with <code>foregroundServiceType="specialUse"</code> (<code>FOREGROUND_SERVICE_SPECIAL_USE</code>) and a low-importance, ongoing notification.</li>
<li>Besides <code>START_STICKY</code>, the overlay is restored on app launch, on process restart, and by a WorkManager watchdog every 15 minutes.</li>
<li>Users can revoke the overlay permission at any time, and adding a window after that throws <code>BadTokenException</code>, so <code>Settings.canDrawOverlays()</code> is checked before every <code>addView</code>.</li>
</ul>
<h2>The permission flow</h2>
<p><code>ACTION_MANAGE_OVERLAY_PERMISSION</code> with a <code>package:</code> URI → retry without it → fall back to <code>ACTION_APPLICATION_DETAILS_SETTINGS</code>. The guide screen checks <code>canDrawOverlays()</code> in <code>onResume</code> and returns as soon as it's true.</p>
<h2>Wrapping up</h2>
<p>Most of the work was in small details that let the overlay sit on top without getting in the way. If you're building an overlay of your own, I hope this saves you some time. The app is here (there's a Windows version too):</p>
<ul>
<li>Google Play: <a href="https://play.google.com/store/apps/details?id=com.codedac.readfocus">https://play.google.com/store/apps/details?id=com.codedac.readfocus</a></li>
<li>Website: <a href="https://codedac.com/apps/readfocus.html">https://codedac.com/apps/readfocus.html</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Android 10's clipboard restriction and serverless phone-to-PC sync: building Clipboard+]]></title><description><![CDATA[I'm a solo developer making small utility apps for Android and Windows. The one with the most users is Clipboard+, a clipboard history manager. Two…]]></description><link>https://codedac.hashnode.dev/android-10-s-clipboard-restriction-and-serverless-phone-to-pc-sync-building-clipboard</link><guid isPermaLink="true">https://codedac.hashnode.dev/android-10-s-clipboard-restriction-and-serverless-phone-to-pc-sync-building-clipboard</guid><category><![CDATA[Android]]></category><category><![CDATA[Java]]></category><category><![CDATA[privacy]]></category><category><![CDATA[Google Drive]]></category><dc:creator><![CDATA[CodeDAC]]></dc:creator><pubDate>Wed, 14 Oct 2026 01:59:41 GMT</pubDate><content:encoded><![CDATA[<p>I'm a solo developer making small utility apps for Android and Windows. The one with the most users is <strong>Clipboard+</strong>, a clipboard history manager. Two problems shaped almost every design decision in it: <strong>Android 10's clipboard restriction</strong> and <strong>syncing between phone and PC without running a server</strong>. Here's how I handled both.</p>
<h2>1. Since Android 10, background apps can't read the clipboard</h2>
<p>From Android 10 (API 29), only the app that currently has <strong>input focus</strong> (or the default keyboard) can read the clipboard. The classic clipboard-manager design, a background service that saves every copy, simply stops working. There are workarounds through accessibility services or log permissions, but given Play policy and user trust I didn't want to go there.</p>
<p>So I changed the promise instead of fighting the platform:</p>
<ul>
<li><p><strong>No "auto-save" claim.</strong> I removed that wording from the store listing too.</p>
</li>
<li><p>Save <strong>every time the app legitimately gets focus</strong>: when the app opens, when you <strong>tap the floating panel</strong>, and when you use the <strong>home-screen widget</strong>. The floating panel is an overlay window that is normally <code>FLAG_NOT_FOCUSABLE</code>; on tap it briefly clears that flag to take focus, reads the clipboard, then gives focus back.</p>
</li>
<li><p>For saving without switching apps, there's an entry in the <strong>text selection menu (</strong><code>ACTION_PROCESS_TEXT</code><strong>)</strong>. Select text anywhere, pick "Clipboard+", and it lands in the history.</p>
</li>
</ul>
<p>You lose "everything you copy just piles up", but "the moment you open the panel to paste, the last thing you copied is saved too" covers most real use.</p>
<h2>2. No sync server</h2>
<p>Clipboards are full of passwords, addresses, account numbers and one-time codes. I didn't want any of that passing through my server, and as a one-person studio I didn't want to run one forever either.</p>
<p>So the storage is <strong>the user's own Google Drive</strong>:</p>
<ul>
<li><p>The Android and Windows apps each connect to the user's Drive and sync through the hidden, app-only <code>appDataFolder</code>. It doesn't show up in the Drive UI, and the app can't see the user's other files. Sync uses the <code>drive.appdata</code> scope; backup files use <code>drive.file</code>, which only covers files the app created.</p>
</li>
<li><p>On Android I skipped the old Drive Android API: <code>AuthorizationClient</code> just gets an access token, and the app calls Drive REST v3 directly. Fewer dependencies, and the same API shape as the Windows side.</p>
</li>
<li><p>No server means no hosting cost, and I can honestly tell users "your data only lives in your own Drive".</p>
</li>
</ul>
<h2>3. Don't hold data hostage when people switch phones</h2>
<p>Backup and restore started as paid features. Keeping someone's data locked when they change phones felt wrong, so <strong>backup file export/import and Drive restore are now free</strong>. Pro is ad removal, live phone-PC sync, unlimited image clips and the larger panel/widget.</p>
<h2>Feedback welcome</h2>
<ul>
<li><p>Cases where something doesn't get saved (which app, which Android version)</p>
</li>
<li><p>Whether the "your own Drive, no server" setup feels trustworthy, or whether the permission screen makes it feel the opposite</p>
</li>
</ul>
<p>Google Play: <a href="https://play.google.com/store/apps/details?id=com.codedac.clipboard">Clipboard+ for Android</a><br />Windows (Microsoft Store): <a href="https://apps.microsoft.com/detail/9N9SRKQV1R6D">Clipboard+ for Windows</a><br />More: <a href="http://codedac.com">codedac.com</a></p>
]]></content:encoded></item></channel></rss>