<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Weavri</title>
    <description>The latest articles on DEV Community by Weavri (@weavrikx).</description>
    <link>https://dev.to/weavrikx</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4193486%2F174318a2-9795-4b67-aa81-2896f196ae97.png</url>
      <title>DEV Community: Weavri</title>
      <link>https://dev.to/weavrikx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/weavrikx"/>
    <language>en</language>
    <item>
      <title>Shipping a signed Android APK as a solo dev (and the WiFi app it was for)</title>
      <dc:creator>Weavri</dc:creator>
      <pubDate>Mon, 19 Oct 2026 11:46:16 +0000</pubDate>
      <link>https://dev.to/weavrikx/shipping-a-signed-android-apk-as-a-solo-dev-and-the-wifi-app-it-was-for-m3p</link>
      <guid>https://dev.to/weavrikx/shipping-a-signed-android-apk-as-a-solo-dev-and-the-wifi-app-it-was-for-m3p</guid>
      <description>&lt;p&gt;I shipped a small Android app this week and I want to talk about the &lt;em&gt;other&lt;/em&gt; half of building it: making the release artifact something a stranger can actually trust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WiFi Beacon (WiFi 哨兵)&lt;/strong&gt; is a tiny utility: you pick a Wi-Fi network, and every time you open an app you've flagged as distracting &lt;em&gt;while connected to that network&lt;/em&gt;, it throws a full-screen wall in front of it. It's the digital equivalent of putting the snacks on a shelf you can't reach.&lt;/p&gt;

&lt;p&gt;It's not exciting. That's the point — it's the kind of app that either works quietly or doesn't get used at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pick a Wi-Fi SSID (the one you work on)&lt;/li&gt;
&lt;li&gt;Pick the apps you don't want to open there&lt;/li&gt;
&lt;li&gt;A foreground-service detector (AccessibilityService) notices the app coming to the foreground&lt;/li&gt;
&lt;li&gt;A full-screen warning covers it: &lt;em&gt;"You're on . You opened . Go back?"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The logic is small. Most of the value is in getting the details right:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting the current foreground app without root.&lt;/strong&gt; &lt;code&gt;ActivityManager.getRunningAppProcesses()&lt;/code&gt; is basically useless post-Android 5. The reliable route is an &lt;code&gt;AccessibilityService&lt;/code&gt; watching &lt;code&gt;TYPE_WINDOW_STATE_CHANGED&lt;/code&gt;. It costs you a scary permission dialog, so the app has to justify it clearly in one sentence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Android 12 broke SSID reads.&lt;/strong&gt; &lt;code&gt;WifiManager.getConnectionInfo()&lt;/code&gt; no longer hands you the SSID the way it used to — you now need &lt;code&gt;ACCESS_FINE_LOCATION&lt;/code&gt; (and on 13+, &lt;code&gt;NEARBY_WIFI_DEVICES&lt;/code&gt;) or the SSID comes back as &lt;code&gt;&amp;lt;unknown ssid&amp;gt;&lt;/code&gt;. That's the single most common bug in this category of app.&lt;/p&gt;

&lt;p&gt;I wrote both of those up in detail before building this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/weavrikx/getting-the-current-foreground-app-on-android-without-root-accessibilityservice-22jj"&gt;Getting the current foreground app on Android without root (AccessibilityService)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/weavrikx/android-12-returns-for-wifi-ssid-the-permission-fix-fh4"&gt;Android 12 returns &lt;code&gt;&amp;lt;unknown ssid&amp;gt;&lt;/code&gt; for WiFi — the permission fix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The part I'd been skipping for years: shipping a &lt;em&gt;signed&lt;/em&gt;, verifiable release
&lt;/h2&gt;

&lt;p&gt;I've shipped debug APKs by hand for years. This time I did it properly, and the friction was all in places I didn't expect (no Android Studio involved — just JDK 17, the Android SDK, and Gradle on a headless Linux box):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;assembleRelease&lt;/code&gt; with no signing config produces an unsigned APK&lt;/strong&gt;, and Gradle will happily give you a success message for it. The signing config has to be conditioned on a properties file that exists outside version control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;services.gradle.org&lt;/code&gt; being unreachable&lt;/strong&gt; doesn't fail loudly — the wrapper just sits there. The fix is getting the distribution into the wrapper cache under the right hash, or pointing at a mirror.&lt;/li&gt;
&lt;li&gt;Verify what you shipped, not what you think you shipped:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apksigner verify &lt;span class="nt"&gt;--print-certs&lt;/span&gt; app/build/outputs/apk/release/app-release.apk
shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256 app-release.apk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wrote that flow up too: &lt;a href="https://dev.to/weavrikx/no-android-studio-build-apks-in-github-actions-and-verify-them-on-real-hardware-bc0"&gt;No Android Studio? Build APKs in GitHub Actions and verify them on real hardware&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;Same app, four ways in — no paywall in front of the code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/weavrikx/wifiwarn" rel="noopener noreferrer"&gt;github.com/weavrikx/wifiwarn&lt;/a&gt; — MIT, build it yourself, do whatever you want with it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signed APK (4.5 MB):&lt;/strong&gt; &lt;a href="https://github.com/weavrikx/wifiwarn/releases/latest" rel="noopener noreferrer"&gt;releases/latest&lt;/a&gt; — built and verified, SHA-256 published alongside it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Landing page:&lt;/strong&gt; &lt;a href="https://weavrikx.github.io/" rel="noopener noreferrer"&gt;weavrikx.github.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The paid version, if you just don't want to compile:&lt;/strong&gt; ¥9.9 (about $1.40) for the ready APK + a setup guide, or a ¥99+ white-label build (your app name, icon, warning copy, preset rules)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why "open source + a $1.40 paid build" instead of one or the other
&lt;/h2&gt;

&lt;p&gt;I went back and forth on this and landed here: the code being MIT isn't a loss leader, it's the honest position — I'd rather someone fork it than bounce off a paywall. The paid tier sells &lt;em&gt;not having to build it&lt;/em&gt;, which is a real thing people pay for. Nobody who wants to &lt;code&gt;git clone&lt;/code&gt; was ever going to be a customer, and pretending otherwise mostly just annoys developers.&lt;/p&gt;

&lt;p&gt;If you've fought the Android 12 SSID permission mess, or you've ever shipped an unsigned APK and only found out later, I'd like to hear how you handled it.&lt;/p&gt;

</description>
      <category>android</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>No Android Studio? Build APKs in GitHub Actions and verify them on real hardware</title>
      <dc:creator>Weavri</dc:creator>
      <pubDate>Mon, 19 Oct 2026 10:14:58 +0000</pubDate>
      <link>https://dev.to/weavrikx/no-android-studio-build-apks-in-github-actions-and-verify-them-on-real-hardware-bc0</link>
      <guid>https://dev.to/weavrikx/no-android-studio-build-apks-in-github-actions-and-verify-them-on-real-hardware-bc0</guid>
      <description>&lt;p&gt;You can ship Android APKs without ever installing Android Studio. This is the loop I use: &lt;strong&gt;CI builds the APK, I pull it back, I verify it on a real device.&lt;/strong&gt; Total cost: $0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not build locally
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Android Studio is huge and GUI-dependent — painful on a headless Linux box&lt;/li&gt;
&lt;li&gt;A cloud runner gives you a clean, reproducible build environment; switching machines changes nothing&lt;/li&gt;
&lt;li&gt;For a small personal project, the free CI minutes are more than enough&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. The GitHub Actions workflow
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.github/workflows/build.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;temurin&lt;/span&gt;
          &lt;span class="na"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;17'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build debug APK&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./gradlew assembleDebug&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apk&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app/build/outputs/apk/debug/*.apk&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push and you get an artifact. You can also trigger it manually from the Actions tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Get the APK back onto your machine
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh run download &amp;lt;run-id&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; apk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Verify on a real device (the step people skip)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Build succeeded" ≠ "it runs."&lt;/strong&gt; Install over adb and actually exercise it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; wifiwarn-debug.apk
adb shell dumpsys accessibility | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; enabled   &lt;span class="c"&gt;# is the service actually up?&lt;/span&gt;
adb logcat &lt;span class="nt"&gt;-s&lt;/span&gt; WarnService                           &lt;span class="c"&gt;# watch the runtime log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I accept builds on a Huawei device running Android 14: install, enable the accessibility service, connect to the target WiFi, open the target app, and confirm all three warning channels fire.&lt;/p&gt;

&lt;p&gt;This is the part that matters. A green CI badge proves the compiler was happy. It proves nothing about behaviour on a real OEM ROM with a real battery optimiser and a real accessibility implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;./gradlew: Permission denied&lt;/code&gt; — commit the wrapper and &lt;code&gt;chmod +x gradlew&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JDK version&lt;/strong&gt;: AGP 8.x needs JDK 17, not the runner's default 11&lt;/li&gt;
&lt;li&gt;Artifacts expire after 90 days by default — promote real releases to a GitHub Release&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Cloud build + real-device acceptance is a cheap way to make every artifact you hand over carry evidence, not just a promise. The build environment is disposable; the verification is not.&lt;/p&gt;

&lt;p&gt;Project: &lt;a href="https://github.com/weavrikx/wifiwarn" rel="noopener noreferrer"&gt;github.com/weavrikx/wifiwarn&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I'm Weavri, an indie Android developer. Cloud builds, real-device testing, reproducible artifacts.&lt;/p&gt;

</description>
      <category>android</category>
      <category>automation</category>
      <category>ci</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Android 12+ returns &lt;unknown ssid&gt; for WiFi SSID — the permission fix</title>
      <dc:creator>Weavri</dc:creator>
      <pubDate>Mon, 19 Oct 2026 10:07:52 +0000</pubDate>
      <link>https://dev.to/weavrikx/android-12-returns-for-wifi-ssid-the-permission-fix-fh4</link>
      <guid>https://dev.to/weavrikx/android-12-returns-for-wifi-ssid-the-permission-fix-fh4</guid>
      <description>&lt;p&gt;You ship a WiFi feature. Users report it "doesn't work". You log the SSID and find this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;unknown ssid&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your code is fine. Your manifest has &lt;code&gt;ACCESS_WIFI_STATE&lt;/code&gt;. And yet. Welcome to the Android 12 permission model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;wifiManager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSystemService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WIFI_SERVICE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;WifiManager&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ssid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wifiManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connectionInfo&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;   &lt;span class="c1"&gt;// "&amp;lt;unknown ssid&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No crash. No exception. Just silent garbage — which is the worst kind of failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The root cause
&lt;/h2&gt;

&lt;p&gt;Starting with &lt;strong&gt;Android 12 (API 31)&lt;/strong&gt;, reading the WiFi SSID requires a &lt;strong&gt;runtime&lt;/strong&gt; permission. Declaring it in the manifest is not enough.&lt;/p&gt;

&lt;p&gt;You have two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ACCESS_FINE_LOCATION&lt;/code&gt; — the legacy route, still works, still prompts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NEARBY_WIFI_DEVICES&lt;/code&gt; — new in &lt;strong&gt;Android 13 (API 33)&lt;/strong&gt;, and the semantically correct one for "I just want to see WiFi devices, not track location"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the permission is declared but not granted, the API returns &lt;code&gt;&amp;lt;unknown ssid&amp;gt;&lt;/code&gt; instead of throwing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getCurrentSsid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;wifiManager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationContext&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSystemService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WIFI_SERVICE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;WifiManager&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;info&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wifiManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connectionInfo&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;removeSurrounding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the runtime request, version-gated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SDK_INT&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;requestPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NEARBY_WIFI_DEVICES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SDK_INT&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;requestPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ACCESS_FINE_LOCATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Three traps I actually hit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. The quote trap.&lt;/strong&gt; &lt;code&gt;connectionInfo.ssid&lt;/code&gt; comes back &lt;em&gt;quoted&lt;/em&gt; — literally &lt;code&gt;"HomeWiFi"&lt;/code&gt; with the double quotes included. Always &lt;code&gt;removeSurrounding("\"")&lt;/code&gt; before comparing, or your SSID match will never fire.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. On newer OEM builds, &lt;code&gt;NEARBY_WIFI_DEVICES&lt;/code&gt; is a hard dependency.&lt;/strong&gt; Tested on a Huawei device running Android 14: without the nearby-devices permission you get &lt;code&gt;&amp;lt;unknown ssid&amp;gt;&lt;/code&gt; even with location granted. Don't assume location alone is enough on API 33+.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Never treat &lt;code&gt;&amp;lt;unknown ssid&amp;gt;&lt;/code&gt; as a valid value.&lt;/strong&gt; Explicitly null-check and reject the sentinel. Otherwise you'll end up with a saved rule named &lt;code&gt;&amp;lt;unknown ssid&amp;gt;&lt;/code&gt; that silently matches nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I used this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WiFi Beacon&lt;/strong&gt; (&lt;code&gt;wifiwarn&lt;/code&gt;) matches a &lt;em&gt;combination&lt;/em&gt;: the currently connected WiFi SSID &lt;strong&gt;and&lt;/strong&gt; the foreground app. When both match, it warns you.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/weavrikx/wifiwarn" rel="noopener noreferrer"&gt;github.com/weavrikx/wifiwarn&lt;/a&gt; — issues and stars welcome.&lt;/p&gt;




&lt;p&gt;I'm Weavri, an indie Android developer. Everything I ship is tested on real hardware (Android 14) and reproducible.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobile</category>
      <category>programming</category>
    </item>
    <item>
      <title>Getting the Current Foreground App on Android Without Root (AccessibilityService)</title>
      <dc:creator>Weavri</dc:creator>
      <pubDate>Mon, 19 Oct 2026 10:05:57 +0000</pubDate>
      <link>https://dev.to/weavrikx/getting-the-current-foreground-app-on-android-without-root-accessibilityservice-22jj</link>
      <guid>https://dev.to/weavrikx/getting-the-current-foreground-app-on-android-without-root-accessibilityservice-22jj</guid>
      <description>&lt;p&gt;If you need to know &lt;strong&gt;which app the user is looking at right now&lt;/strong&gt; — for a focus timer, a parental-control tool, an anti-distraction app — you hit the same wall at step one: &lt;em&gt;how do you get the current foreground app without root?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I shipped a tool that does exactly this, and this post is the part I wish someone had written for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The obvious options, and why they all fail
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ActivityManager.getRunningTasks()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deprecated since Android 5.0. Returns only your own task.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getRunningAppProcesses()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns only your own process (Android 8+).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;UsageStatsManager&lt;/code&gt; ("Usage access")&lt;/td&gt;
&lt;td&gt;Works, but latency is &lt;strong&gt;up to ~10 minutes&lt;/strong&gt;. Useless for real-time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;AccessibilityService&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TYPE_WINDOW_STATE_CHANGED&lt;/code&gt; fires the instant the window changes. ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;AccessibilityService&lt;/code&gt; is the &lt;strong&gt;only&lt;/strong&gt; real-time, no-root option.&lt;/p&gt;

&lt;p&gt;The price: the user must enable the service manually (Settings → Accessibility). There is no legitimate way to toggle it programmatically, and you shouldn't try — that's a Play-policy landmine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minimal working implementation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WarnService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AccessibilityService&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onAccessibilityEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AccessibilityEvent&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;eventType&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="nc"&gt;AccessibilityEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TYPE_WINDOW_STATE_CHANGED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pkg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;packageName&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;packageName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;   &lt;span class="c1"&gt;// ignore our own windows&lt;/span&gt;

        &lt;span class="nf"&gt;checkAndWarn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onInterrupt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The manifest — every attribute matters here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;service&lt;/span&gt;
    &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;".WarnService"&lt;/span&gt;
    &lt;span class="na"&gt;android:exported=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
    &lt;span class="na"&gt;android:permission=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.BIND_ACCESSIBILITY_SERVICE"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;intent-filter&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.accessibilityservice.AccessibilityService"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/intent-filter&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta-data&lt;/span&gt;
        &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.accessibilityservice"&lt;/span&gt;
        &lt;span class="na"&gt;android:resource=&lt;/span&gt;&lt;span class="s"&gt;"@xml/accessibility_config"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/service&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;res/xml/accessibility_config.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;accessibility-service&lt;/span&gt; &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;
    &lt;span class="na"&gt;android:accessibilityEventTypes=&lt;/span&gt;&lt;span class="s"&gt;"typeWindowStateChanged"&lt;/span&gt;
    &lt;span class="na"&gt;android:accessibilityFeedbackType=&lt;/span&gt;&lt;span class="s"&gt;"feedbackGeneric"&lt;/span&gt;
    &lt;span class="na"&gt;android:notificationTimeout=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Four pitfalls that actually bit me
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;TYPE_WINDOW_STATE_CHANGED&lt;/code&gt;, not &lt;code&gt;TYPE_WINDOW_CONTENT_CHANGED&lt;/code&gt;.&lt;/strong&gt; The latter fires on every content update — your battery graph will look like a ski slope.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Guard against self-triggering.&lt;/strong&gt; When your own warning overlay appears, that's &lt;em&gt;also&lt;/em&gt; a window-state change. Filter out your own package name or you get an infinite loop.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Throttle.&lt;/strong&gt; A 30-second cooldown after each warning. Without it, a user who is genuinely distracted gets spammed and uninstalls.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't try to block.&lt;/strong&gt; Blocking an app launch requires root. "Detect + notify" is the correct no-root design — and it keeps you on the right side of Play policy.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I built with it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WiFi Beacon&lt;/strong&gt; (&lt;code&gt;wifiwarn&lt;/code&gt;): you configure a target WiFi SSID + a target app. When the phone is connected to that WiFi &lt;strong&gt;and&lt;/strong&gt; the app opens, it fires a sound, a notification, and a full-screen overlay.&lt;/p&gt;

&lt;p&gt;Use cases: a parent policing games on the home WiFi; or self-discipline when you're supposed to be working on the office WiFi.&lt;/p&gt;

&lt;p&gt;The whole thing runs locally — no cloud, no data collection, Android 8–15.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/weavrikx/wifiwarn" rel="noopener noreferrer"&gt;github.com/weavrikx/wifiwarn&lt;/a&gt; — stars and issues are welcome.&lt;/p&gt;




&lt;p&gt;I'm Weavri, an indie Android developer. I test on real hardware (Android 14) and ship reproducible builds.&lt;/p&gt;

</description>
      <category>android</category>
      <category>mobile</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
