<?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: Rexa</title>
    <description>The latest articles on DEV Community by Rexa (@rexa).</description>
    <link>https://dev.to/rexa</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%2F4051969%2Faf535407-5955-42ab-ae86-fa1c434125d0.png</url>
      <title>DEV Community: Rexa</title>
      <link>https://dev.to/rexa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rexa"/>
    <language>en</language>
    <item>
      <title>Blocking apps on iOS vs Android: I built the same feature twice and they have nothing in common</title>
      <dc:creator>Rexa</dc:creator>
      <pubDate>Tue, 28 Jul 2026 20:04:53 +0000</pubDate>
      <link>https://dev.to/rexa/blocking-apps-on-ios-vs-android-i-built-the-same-feature-twice-and-they-have-nothing-in-common-1n06</link>
      <guid>https://dev.to/rexa/blocking-apps-on-ios-vs-android-i-built-the-same-feature-twice-and-they-have-nothing-in-common-1n06</guid>
      <description>&lt;p&gt;I spent a year building a focus app with an app blocker in it, which meant writing the feature once for Android and once for iOS. I expected two implementations of the same idea. What I got was two ideas that happen to produce the same screenshot.&lt;/p&gt;

&lt;p&gt;The short version: &lt;strong&gt;on Android you build a blocker. On iOS you ask for one.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Android: you detect, you enforce
&lt;/h2&gt;

&lt;p&gt;You poll &lt;code&gt;UsageStatsManager&lt;/code&gt; from a foreground service to find out which app is in front, compare it against your blocked list, and launch your own translucent activity over the top.&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="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;events&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lastQueryTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&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;event&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UsageEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNextEvent&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getNextEvent&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="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="p"&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;UsageEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MOVE_TO_FOREGROUND&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;currentForeground&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="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;&lt;code&gt;event.packageName&lt;/code&gt; is a plain string. &lt;code&gt;com.instagram.android&lt;/code&gt;. You know exactly what the user opened, you know when, and you could count it, log it, or send it somewhere. The platform trusts you not to.&lt;/p&gt;

&lt;p&gt;Everything downstream is yours: your own app picker built off &lt;code&gt;getInstalledApplications&lt;/code&gt;, your own lock screen with whatever countdown ring and copy you want, your own rules about grace periods and escape hatches.&lt;/p&gt;

&lt;p&gt;The bill comes as operational work. A foreground service with a &lt;code&gt;specialUse&lt;/code&gt; type and a justification string for the reviewer. A persistent notification you can't remove. Roughly a second of latency from the poll interval. Debouncing, because an app fires several foreground events while opening and your lock will strobe without a guard. And OEM battery managers that kill your service anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  iOS: you never find out anything
&lt;/h2&gt;

&lt;p&gt;Here's the entire enforcement path on iOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;sharedStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationTokens&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. There is no service, no polling, no lock screen of yours, no detection of any kind. You hand &lt;code&gt;ManagedSettingsStore&lt;/code&gt; a set of tokens and the OS does the shielding, at a level your process never sees.&lt;/p&gt;

&lt;p&gt;The thing that took me longest to accept is what &lt;code&gt;selection.applicationTokens&lt;/code&gt; actually contains. It's a set of &lt;code&gt;ApplicationToken&lt;/code&gt; - &lt;strong&gt;opaque&lt;/strong&gt;. Not a bundle ID. Not a name. Not an icon. You cannot decode it, log it, compare it to a list you hold, or find out which app the user picked. Your app blocks Instagram without ever learning that Instagram is installed.&lt;/p&gt;

&lt;p&gt;Which means the picker can't be yours either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;FamilyActivityPicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;newSelection&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newSelection&lt;/span&gt;
        &lt;span class="kt"&gt;AppLockModule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sharedStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;newSelection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationTokens&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEmpty&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;newSelection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationTokens&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;&lt;code&gt;FamilyActivityPicker&lt;/code&gt; is Apple's SwiftUI view. You present it and get tokens back. You can't style the rows, can't search it your way, can't show the user their blocked apps afterwards in your own design - because you don't know what they are. The best you can honestly show is a count.&lt;/p&gt;

&lt;p&gt;Getting authorization is one call, and it's all-or-nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;AuthorizationCenter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestAuthorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;individual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The gotcha nobody mentions: persisting the tokens
&lt;/h2&gt;

&lt;p&gt;Tokens are opaque but they are &lt;code&gt;Codable&lt;/code&gt;, and you must store them yourself - and store them somewhere your extensions can reach, which means an App Group, not &lt;code&gt;UserDefaults.standard&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;suiteName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"group.com.yourapp"&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;saveSelection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;FamilyActivitySelection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;JSONEncoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;groupDefaults&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;selectionKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;restoreSavedSelection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;groupDefaults&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;selectionKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;JSONDecoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;FamilyActivitySelection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sharedStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationTokens&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I shipped the standard-&lt;code&gt;UserDefaults&lt;/code&gt; version first and had to write a migration when the extensions needed the same data. Start with the App Group.&lt;/p&gt;

&lt;p&gt;Note the restore call has to run on init. Shields survive app termination, but your in-memory selection doesn't, and if you clear or re-apply without restoring first you silently unblock everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade, in one line each
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Android&lt;/th&gt;
&lt;th&gt;iOS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who enforces&lt;/td&gt;
&lt;td&gt;you&lt;/td&gt;
&lt;td&gt;the OS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What you learn&lt;/td&gt;
&lt;td&gt;the package name of everything opened&lt;/td&gt;
&lt;td&gt;nothing at all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The lock screen&lt;/td&gt;
&lt;td&gt;yours&lt;/td&gt;
&lt;td&gt;Apple's, unless you ship a shield extension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The picker&lt;/td&gt;
&lt;td&gt;yours&lt;/td&gt;
&lt;td&gt;Apple's, non-negotiable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Running cost&lt;/td&gt;
&lt;td&gt;foreground service, notification, battery&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;~1s (poll interval)&lt;/td&gt;
&lt;td&gt;instant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum version&lt;/td&gt;
&lt;td&gt;any, in practice&lt;/td&gt;
&lt;td&gt;iOS 16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I actually think about it
&lt;/h2&gt;

&lt;p&gt;Apple's design is more privacy-preserving than mine and it isn't close. On Android I chose &lt;code&gt;UsageStatsManager&lt;/code&gt; over an &lt;code&gt;AccessibilityService&lt;/code&gt; specifically to avoid reading the user's screen, and I still end up knowing every app they open. On iOS I couldn't collect that data if I wanted to, because the API never gives it to me. The guarantee is structural rather than a promise in a privacy policy.&lt;/p&gt;

&lt;p&gt;The cost is that the iOS version is less mine. I can't design the moment that matters - the screen someone sees when they reach for the app out of habit - without shipping a separate shield extension, and even then within Apple's frame. On Android that screen is the product.&lt;/p&gt;

&lt;p&gt;If you're building this: expect two codebases, not one abstraction with two backends. I tried the abstraction first. The shared interface ended up being three methods (&lt;code&gt;requestPermissions&lt;/code&gt;, &lt;code&gt;showPicker&lt;/code&gt;, &lt;code&gt;setShieldActive&lt;/code&gt;) and every one of them means something different on each side.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is from building &lt;a href="https://takeover.cloud" rel="noopener noreferrer"&gt;Takeover&lt;/a&gt;, a focus app where the blocker is the point - both store links are there if you want to see how the two feel side by side. I'm the developer, so weigh the opinions accordingly.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>android</category>
      <category>swift</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>How to block apps on Android without an AccessibilityService</title>
      <dc:creator>Rexa</dc:creator>
      <pubDate>Tue, 28 Jul 2026 19:42:30 +0000</pubDate>
      <link>https://dev.to/rexa/how-to-block-apps-on-android-without-an-accessibilityservice-5gog</link>
      <guid>https://dev.to/rexa/how-to-block-apps-on-android-without-an-accessibilityservice-5gog</guid>
      <description>&lt;p&gt;Almost every app blocker on the Play Store is built on an &lt;code&gt;AccessibilityService&lt;/code&gt;. It's the obvious choice: you get a callback every time a window changes, you read the package name, and if it's on the blocked list you throw something over it. Thirty lines and you're done.&lt;/p&gt;

&lt;p&gt;The problem is what you had to ask for to get there. An &lt;code&gt;AccessibilityService&lt;/code&gt; can read the content of every screen the user looks at, including their banking app. Google's policy position is that if the narrower API can do the job, you use the narrower API - and for app blocking it can. Reviewers do reject blockers over this, and users are increasingly suspicious of the permission screen regardless.&lt;/p&gt;

&lt;p&gt;Here's the version without it. Everything below is running in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;Poll &lt;code&gt;UsageStatsManager&lt;/code&gt; from a foreground service to learn which app is in the foreground, and launch a translucent activity over anything on the blocked list. Three permissions:&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;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.PACKAGE_USAGE_STATS"&lt;/span&gt;
    &lt;span class="na"&gt;tools:ignore=&lt;/span&gt;&lt;span class="s"&gt;"ProtectedPermissions"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.SYSTEM_ALERT_WINDOW"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.FOREGROUND_SERVICE"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.FOREGROUND_SERVICE_SPECIAL_USE"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PACKAGE_USAGE_STATS&lt;/code&gt; isn't a runtime permission. You can't request it with a dialog - the user has to grant it in Settings, and you check it through &lt;code&gt;AppOpsManager&lt;/code&gt;:&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;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;hasUsageStatsPermission&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&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;appOps&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;APP_OPS_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;AppOpsManager&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;mode&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="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="nc"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION_CODES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;appOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsafeCheckOpNoThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;AppOpsManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OPSTR_GET_USAGE_STATS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;myUid&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;packageName&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="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;@Suppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DEPRECATION"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="n"&gt;appOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkOpNoThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;AppOpsManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OPSTR_GET_USAGE_STATS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;myUid&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;packageName&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;AppOpsManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MODE_ALLOWED&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send them there with &lt;code&gt;Settings.ACTION_USAGE_ACCESS_SETTINGS&lt;/code&gt;. Budget for the fact that this is a real drop-off point in onboarding - it's a scary-looking screen buried in special app access, and no amount of copy makes it feel routine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the foreground app
&lt;/h2&gt;

&lt;p&gt;The core loop. A foreground service with a &lt;code&gt;Handler&lt;/code&gt; posting itself every 900ms:&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;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pollRunnable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;Runnable&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;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;checkForeground&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postDelayed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;900L&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;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;checkForeground&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;usm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usageStatsManager&lt;/span&gt; &lt;span class="o"&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;now&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;try&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;events&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lastQueryTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&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;event&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UsageEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNextEvent&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getNextEvent&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="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="p"&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;UsageEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MOVE_TO_FOREGROUND&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;currentForeground&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="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;lastQueryTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;
    &lt;span class="c1"&gt;// ...check currentForeground against the blocked set&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details in there matter more than they look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;queryEvents&lt;/code&gt; returns events, not state.&lt;/strong&gt; If the user has been sitting in the same app for a minute, nothing changed, so the query legitimately returns nothing. This is the single most common bug in this approach - people query a fixed window, get an empty result, reset their state to "no foreground app", and the lock flickers or drops. Keep the last known package in a field and only overwrite it when an event actually arrives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use a rolling window, not a fixed one.&lt;/strong&gt; &lt;code&gt;[lastQueryTime, now]&lt;/code&gt;, with &lt;code&gt;lastQueryTime&lt;/code&gt; advanced only after a successful read. The event buffer isn't strictly realtime and things can land a few hundred milliseconds late; a fixed &lt;code&gt;[now - 1000, now]&lt;/code&gt; window drops those. On startup, backdate it a couple of seconds, or you'll miss the transition that happened just before monitoring began.&lt;/p&gt;

&lt;h2&gt;
  
  
  Showing the lock
&lt;/h2&gt;

&lt;p&gt;A normal &lt;code&gt;Activity&lt;/code&gt;, declared translucent, single instance, and kept out of recents:&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;activity&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;".applock.LockOverlayActivity"&lt;/span&gt;
    &lt;span class="na"&gt;android:theme=&lt;/span&gt;&lt;span class="s"&gt;"@android:style/Theme.Translucent.NoTitleBar.Fullscreen"&lt;/span&gt;    &lt;span class="na"&gt;android:excludeFromRecents=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
    &lt;span class="na"&gt;android:launchMode=&lt;/span&gt;&lt;span class="s"&gt;"singleInstance"&lt;/span&gt;
    &lt;span class="na"&gt;android:exported=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Launched with &lt;code&gt;FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TOP or FLAG_ACTIVITY_SINGLE_TOP&lt;/code&gt;. &lt;code&gt;singleInstance&lt;/code&gt; plus &lt;code&gt;SINGLE_TOP&lt;/code&gt; is what stops you stacking six copies of the lock while an app is still opening.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three guards you will end up writing
&lt;/h2&gt;

&lt;p&gt;None of these are in the tutorials, and all three are the difference between "works on my emulator" and "works on someone's phone".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An app fires several foreground events while opening.&lt;/strong&gt; Without a guard you launch the lock, the app's next window event arrives, you launch it again, and the user watches it strobe. Track whether the lock is already visible with a &lt;code&gt;@Volatile&lt;/code&gt; flag on the activity's companion, and skip re-launching for the same package within a second.&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;LockOverlayActivity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isShowing&lt;/span&gt;&lt;span class="p"&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;packageName&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;lastLockApp&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lastLockTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Returning to your own app is itself a transition.&lt;/strong&gt; When the user taps "go back", the blocked app briefly surfaces again while the task switches. Write a short transition marker before you leave and ignore that package for ~3 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Give people a real way out.&lt;/strong&gt; If someone deliberately unlocks an app, honour it - a grace period keyed to that package, five minutes or so. A blocker with no escape hatch gets uninstalled, not obeyed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Foreground service type
&lt;/h2&gt;

&lt;p&gt;On Android 14+ you must declare a type. App blocking doesn't fit any of the specific ones, so it's &lt;code&gt;specialUse&lt;/code&gt;, and that requires a property explaining yourself to the reviewer:&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;".applock.AppLockMonitorService"&lt;/span&gt;
    &lt;span class="na"&gt;android:exported=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;
    &lt;span class="na"&gt;android:foregroundServiceType=&lt;/span&gt;&lt;span class="s"&gt;"specialUse"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"&lt;/span&gt;
        &lt;span class="na"&gt;android:value=&lt;/span&gt;&lt;span class="s"&gt;"Enforces user-selected app blocking during a focus session by detecting the foreground app via usage access"&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;Start it with &lt;code&gt;startForegroundService&lt;/code&gt; and call &lt;code&gt;startForeground&lt;/code&gt; with &lt;code&gt;FOREGROUND_SERVICE_TYPE_SPECIAL_USE&lt;/code&gt; on API 34+. Return &lt;code&gt;START_STICKY&lt;/code&gt; so the OS brings monitoring back if it kills you mid-session.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you give up
&lt;/h2&gt;

&lt;p&gt;Being straight about the trade-offs, because they're real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency.&lt;/strong&gt; Polling at 900ms means up to about a second between opening a blocked app and the lock appearing. An &lt;code&gt;AccessibilityService&lt;/code&gt; is effectively instant. In practice a second is fine for the use case - the user sees their feed for a moment and then doesn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A permission users have to find themselves.&lt;/strong&gt; No dialog, no one-tap grant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A persistent notification.&lt;/strong&gt; Foreground service, no way around it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OEM behaviour varies.&lt;/strong&gt; Aggressive battery management on some skins will kill the service. &lt;code&gt;START_STICKY&lt;/code&gt; helps; it isn't a cure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it buys you is that you never asked to read the user's screen. For a blocker, that's the right trade - both for review and for the person installing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this doesn't solve
&lt;/h2&gt;

&lt;p&gt;This is not parental-control-grade enforcement and shouldn't be sold as such. Anyone can go to Settings and revoke usage access, and the lock is gone. The design goal isn't to make bypassing impossible, it's to make it cost about thirty seconds of deliberate effort - which turns out to be longer than the urge to open Instagram usually lasts.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build &lt;a href="https://play.google.com/store/apps/details?id=com.hamzacakmak.myApp" rel="noopener noreferrer"&gt;Takeover&lt;/a&gt;, where this runs - so take the framing with that in mind. The approach isn't specific to it, and I'd rather more blockers used it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobile</category>
      <category>privacy</category>
    </item>
  </channel>
</rss>
