Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
44b4390
feat: let automation apps set the protection flag
thiagosoeiro Sep 9, 2026
8be82f2
build: sign release with a stable key when CI secrets are set
thiagosoeiro Sep 9, 2026
8655d0a
build: state the keystore type explicitly
thiagosoeiro Sep 9, 2026
2c7cf96
Merge pull request #1 from thiagosoeiro/feature/automation-intents
thiagosoeiro Sep 10, 2026
64fbb7d
feat: prompt for biometrics without waiting for a tap
thiagosoeiro Sep 10, 2026
e640ad3
Merge pull request #2 from thiagosoeiro/feature/auto-prompt-biometrics
thiagosoeiro Sep 10, 2026
70fccac
fix: keep an app unlocked when you minimise it and come straight back
thiagosoeiro Sep 10, 2026
f5aa5a3
Merge pull request #3 from thiagosoeiro/fix/keep-unlock-on-return
thiagosoeiro Sep 10, 2026
907ddac
feat: disguise the app as System Services
thiagosoeiro Sep 10, 2026
b80e67a
fix: block our own App info page on OEMs that title it generically
thiagosoeiro Sep 10, 2026
528002e
docs: document the disguise and anti-uninstall behaviour
thiagosoeiro Sep 10, 2026
f8680ba
feat: make the biometric prompt read like a native system prompt
thiagosoeiro Sep 10, 2026
5d9935c
fix: stop anti-uninstall locking the phone while browsing Settings
thiagosoeiro Sep 10, 2026
e1e1b3c
Merge pull request #4 from thiagosoeiro/feature/disguise-system-services
thiagosoeiro Sep 10, 2026
fcae9e7
fix: stop granting device admin from crashing the app
thiagosoeiro Sep 11, 2026
10d89dd
fix: re-lock unlocked apps when protection is switched back on in the…
thiagosoeiro Sep 11, 2026
4541a14
fix: accept only strong biometrics for unlocking
thiagosoeiro Sep 11, 2026
0855247
fix: keep the anti-uninstall guards working after a language change
thiagosoeiro Sep 11, 2026
8aff4bd
fix: keep the anti-uninstall toggle in Settings up to date
thiagosoeiro Sep 11, 2026
1a77897
Merge pull request #5 from thiagosoeiro/fix/security-quick-fixes
thiagosoeiro Sep 11, 2026
f83f197
fix: make the user wait after too many wrong unlock attempts
thiagosoeiro Sep 11, 2026
70efb2c
fix: show the lockout countdown on the lock screens
thiagosoeiro Sep 11, 2026
8f790a9
fix: tidy the lock screens when a wait ends
thiagosoeiro Sep 11, 2026
fa1e06b
Merge pull request #6 from thiagosoeiro/fix/security-rate-limiting
thiagosoeiro Sep 11, 2026
a5d4dee
fix: store the unlock pattern as a salted hash
thiagosoeiro Sep 11, 2026
1c17e43
fix: tie stored PIN and pattern hashes to a key that stays on the phone
thiagosoeiro Sep 11, 2026
b11b75c
docs: add the security audit and the record of its fixes
thiagosoeiro Sep 11, 2026
a62b725
Merge pull request #7 from thiagosoeiro/fix/security-credential-storage
thiagosoeiro Sep 11, 2026
16b89d2
feat: open locked apps without authentication on trusted Wi-Fi
thiagosoeiro Sep 11, 2026
b6911d6
feat: add trusted Wi-Fi settings
thiagosoeiro Sep 11, 2026
3adb41d
feat: show HOME on the shield while trusted Wi-Fi keeps apps open
thiagosoeiro Sep 11, 2026
a281900
docs: describe built-in trusted Wi-Fi
thiagosoeiro Sep 11, 2026
5aa1ba4
Merge pull request #8 from thiagosoeiro/feature/trusted-wifi
thiagosoeiro Sep 11, 2026
3701413
docs: list future improvement ideas
thiagosoeiro Sep 11, 2026
3bf2c94
docs: add intruder video, alerts, log sending and remote lock ideas
thiagosoeiro Sep 11, 2026
6f2ed32
docs: add screen timeout and lock-screen notifications by network
thiagosoeiro Sep 11, 2026
c5b447d
Merge pull request #9 from thiagosoeiro/docs/future-improvements
thiagosoeiro Sep 11, 2026
28d8a89
feat: log what trusted Wi-Fi sees, without network names
thiagosoeiro Sep 11, 2026
4492a8f
docs: say trusted Wi-Fi follows the connection, even at one bar
thiagosoeiro Sep 12, 2026
73e0db8
Merge pull request #10 from thiagosoeiro/diag/trusted-wifi-logging
thiagosoeiro Sep 12, 2026
504791d
ui: remove original-owner links from Settings and Main screens
thiagosoeiro Sep 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ jobs:
build:
runs-on: ubuntu-latest

env:
APPLOCK_KEYSTORE_BASE64: ${{ secrets.APPLOCK_KEYSTORE_BASE64 }}
APPLOCK_KEYSTORE_PASSWORD: ${{ secrets.APPLOCK_KEYSTORE_PASSWORD }}
APPLOCK_KEY_ALIAS: ${{ secrets.APPLOCK_KEY_ALIAS }}
APPLOCK_KEY_PASSWORD: ${{ secrets.APPLOCK_KEY_PASSWORD }}

steps:
- name: Checkout repository
uses: actions/checkout@v4.2.2
Expand All @@ -28,6 +34,12 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4.4.1

- name: Decode signing keystore
if: env.APPLOCK_KEYSTORE_BASE64 != ''
run: |
echo "$APPLOCK_KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.jks"
echo "APPLOCK_KEYSTORE=$RUNNER_TEMP/release.jks" >> "$GITHUB_ENV"

- name: Build with gradle
run: ./gradlew clean assemble

Expand Down
70 changes: 70 additions & 0 deletions ANTI_UNINSTALL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Disguise and anti-uninstall

This fork hardens AppLock against a thief who steals an unlocked phone and wants the lock gone. It
does two things: it stops looking like a lock app, and it resists the ways an app is normally
removed or stopped.

## Disguise

Outside the PIN screen the app presents itself as a system component, so a thief scanning the
launcher or Settings has nothing obvious to remove:

- **Name:** *System Services* (Arabic: *خدمات النظام*).
- **Icon:** a plain gear, matching themed icons on Android 13+.
- **Notification:** the background-service notification reads *System Services / Running*, not
"Protecting your apps".
- **Recents:** on Android 13+ the app shows a blank thumbnail instead of your list of locked apps.

The disguise is **fixed at build time, not a setting.** Only the launcher entry could be swapped at
runtime; Settings → Apps, Accessibility, Device admin apps and the uninstall dialog all read the
name baked into the APK, which is exactly where a thief goes to remove the app. Changing the
disguise means editing `app_name` and the launcher icon and rebuilding.

Two things are deliberately left alone:

- **Screens behind the PIN** (intro, settings, dialogs) still say AppLock. You only reach them after
unlocking, and the PIN pad itself carries no branding.
- **The package name** `dev.pranav.applock` is unchanged, so the update installs over your existing
app and keeps its settings and permissions. It shows only in advanced App info or over `adb`.

## Anti-uninstall

Turn on **Settings → Anti-Uninstall Protection**. It needs two permissions — Device Administrator
and the Accessibility Service — and with both granted the app resists removal.

With it **on**, these are what each Settings surface does:

| On the phone, go to | What happens |
|---|---|
| Settings → Accessibility → System Services | Sent to the home screen and the screen locks |
| Settings → Apps → System Services | Bounced off the page and the screen locks |
| Settings → Apps → System Services → Uninstall | Uninstall is blocked by device admin |
| Device admin apps (path varies by phone) | Bounced off, with the toast "This action isn't allowed." |
| Ordinary pages (Wi-Fi, Display, …) | Nothing happens; they work normally |

> **It blocks you, too.** These pages bounce whoever opens them, including you. To change any of
> these settings yourself, first turn Anti-Uninstall Protection **off** — that asks for your PIN —
> and then open the page.

### How each button is covered

- **Uninstall** — blocked by Android for as long as Device Admin is active: the app can't be removed
until its admin is turned off, and the page for turning it off bounces you (see the table above).
- **Force stop** — greyed out by Android for any app with an active device admin. It briefly becomes
tappable only while device admin is off (for example, right after an update, before the app
re-arms).
- **Clear data / Force stop / anything else on App info** — the accessibility service bounces you
off the app's own App info page before you can reach them.

### Limitations

- **OEM variance.** Settings pages are named differently on each skin. The detection is tuned
against stock Android and One UI (tested on a Galaxy S24 Ultra, One UI 8.5); another skin may
title a page differently and slip past. If a page isn't caught on your phone, enable
**Settings → Advanced → Logging**, reproduce it, and export the security log — the class name and
on-screen text in there are what the check needs.
- **The disguise name is generic.** The App info bounce fires when "System Services" appears on a
Settings page as it opens. If some unrelated page happens to contain that text, it could bounce
you off it too.
- **Not tamper-proof.** A determined attacker with `adb`, recovery, or safe mode can still remove
the app. This raises the bar against an opportunistic thief, not a forensic one.
153 changes: 153 additions & 0 deletions AUTOMATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Automation control

This fork exposes AppLock's global protection flag to automation apps, so that protection can
follow a condition you choose — a trusted Wi-Fi network, a time of day, a location — instead of
being switched by hand.

The intents add no conditions of their own. AppLock only accepts "protection on" and "protection
off"; MacroDroid, Tasker or `adb` decides when to send them. For the most common condition, a
trusted Wi-Fi network, there is also a [built-in option](#built-in-trusted-wi-fi) that needs no
automation app.

## Setup

1. Open **Settings → Automation** and turn on **Allow automation apps**.
2. Tap **Access token**, then **Copy token**.

The receiver stays disabled at the package level until that switch is on, so with the feature off
there is nothing exported to reach.

## Intents

| Action | Effect |
| --- | --- |
| `dev.pranav.applock.action.ENABLE_PROTECTION` | Protection on, and any app unlocked while it was off is re-locked |
| `dev.pranav.applock.action.DISABLE_PROTECTION` | Protection off |
| `dev.pranav.applock.action.QUERY_PROTECTION_STATE` | No change; replies with the current state |

Every action requires the string extra `token`, matching the token from Settings. Requests with a
wrong, missing or stale token are ignored, as is anything sent while automation access is off.

These are **setters, not a toggle**, on purpose. An automation app cannot read AppLock's state, so
a toggle would force it to track state of its own — and that tracked state drifts out of sync the
first time anything is missed, leaving protection off when you believe it is on. `ENABLE` and
`DISABLE` are idempotent: sending either twice is harmless, and no state can desync.

Broadcasts must target the package explicitly. Android does not deliver implicit broadcasts to
manifest-declared receivers, so a broadcast with only an action set will silently do nothing.

## Reading the state back

Whenever protection changes — by intent or by the shield toggle in the app — AppLock broadcasts:

```
dev.pranav.applock.action.PROTECTION_STATE boolean extra "state"
```

This one is implicit, so a dynamically registered receiver picks it up. In MacroDroid that is the
**Intent Received** trigger. Use it to verify rather than assume.

Ordered senders can also read the result code directly: `0` rejected, `1` protection off,
`2` protection on.

## MacroDroid

Two macros, no variables, nothing to keep in sync:

- **Trigger** Wi-Fi → Connected to *your SSID* → **Action** Send Intent
- **Trigger** Wi-Fi → Disconnected from *your SSID* → **Action** Send Intent

Send Intent settings:

| Field | Value |
| --- | --- |
| Target | Broadcast |
| Action | `dev.pranav.applock.action.DISABLE_PROTECTION` (or `ENABLE_…`) |
| Package | `dev.pranav.applock` |
| Class | `dev.pranav.applock.core.broadcast.AutomationReceiver` |
| Extra | name `token`, type String, value *your token* |

Fail closed: put the `ENABLE` macro on *disconnect*, not on "connected to any other network", so
that losing Wi-Fi entirely still re-enables protection.

## Testing with adb

```sh
TOKEN=<token from Settings>

adb shell am broadcast \
-a dev.pranav.applock.action.DISABLE_PROTECTION \
-n dev.pranav.applock/.core.broadcast.AutomationReceiver \
--es token "$TOKEN"

adb shell am broadcast \
-a dev.pranav.applock.action.QUERY_PROTECTION_STATE \
-n dev.pranav.applock/.core.broadcast.AutomationReceiver \
--es token "$TOKEN"
```

`am broadcast` sends an ordered broadcast, so it prints the result code and data.
Rejected requests return `result=0`; enable **Settings → Advanced → Logging** to see the reason in
the audit log.

## Security notes

Anything that can switch protection off is worth being careful about. Three things guard it:

- the receiver component is disabled until you opt in, so by default it is not part of the app's
exported surface at all;
- every action requires the token, compared in constant time;
- **Replace** in the token dialog invalidates the old token immediately.

Anyone who can already run `adb` against an unlocked device could read the token out of the app's
private storage — the token protects against other *apps* on the device, not against someone
holding an unlocked phone with USB debugging on.

## Built-in trusted Wi-Fi

For the most common condition no automation app is needed. **Settings → Trusted Wi-Fi** lets locked
apps open without authentication while the phone is connected to a network you trust, and keeps
them locked everywhere else.

### Setup

1. Turn on **Open locked apps on trusted Wi-Fi**. Grant precise location, then choose **Allow all
the time**, and keep Location switched on.
2. Connect to the network, open **Trusted networks**, and tap **Add**.

Android only shares the name of the connected Wi-Fi network with apps that have location access,
and only in the background if that access is "Allow all the time". The app reads the network name
and nothing else; it never asks for your position.

The shield on the main screen shows **HOME** while a trusted network is keeping apps open.

### Fails closed

Anything the app can't confirm counts as untrusted, so apps stay locked:

- Trust is kept in memory, never stored. After a reboot or restart, apps stay locked until Android
reports a trusted network.
- Wi-Fi off, no connection, an untrusted network, Location off, or location access missing or set
to "While using the app": locked.
- Leaving a trusted network re-locks everything, the same as turning the shield back on.
- Each time the screen turns on or the phone unlocks, the app reads the network name again. If it
can't, or the network isn't trusted, apps lock at once. This check never unlocks anything.

Anti-uninstall is not affected by any of this.

### With the intents

Both can be used together. The intents turn the shield on and off. Trusted Wi-Fi never changes the
shield; it only keeps apps open while the shield is on. With the shield off, apps open everywhere,
trusted network or not. `QUERY_PROTECTION_STATE` and `PROTECTION_STATE` report the shield only.

### Limits

- Networks are matched by name. Someone holding your unlocked phone could name a hotspot the same,
connect the phone to it, and open locked apps.
- Trust follows the Wi-Fi connection, not which network carries your internet. Near the edge of
range the phone can stay connected to a trusted network at one bar while it uses mobile data.
Until the Wi-Fi disconnects, locked apps keep opening and the shield keeps showing **HOME**. The
screen-on check doesn't catch this, because the phone really is still connected. Walking on ends
it once the Wi-Fi drops; staying within range, say just outside or in a building's shared areas,
keeps it going.
Loading