Two people ask the same question and get incompatible answers, because they are standing in different places. One has the system VPN settings screen open and is editing a list of applications. The other is writing VpnService.Builder.addDisallowedApplication() in an editor. Both call it a per-app VPN filter. Only one of them owns the control they are describing, and neither of them is blocking anything.

Start at the screen the user sees

Open Settings, find the VPN entry, and look at the connection that is actually active. If the client offers a per-app list, that list belongs to the client's own tunnel. The platform does not publish a general API through which an unrelated application can read or edit another VPN's application selection, so a second app cannot fix that list for the user.

This is the first fork in the walkthrough. If the installed VPN has no such setting, there is nothing to code around: the feature is absent from the product that owns the tunnel. If it does have one, the right fix is in that screen, made by the person who can open it.

Follow the packet into a tunnel

When your own app builds a tunnel, it creates a TUN interface and declares which applications' traffic enters it. The membership calls happen before the interface is established, and after that the rule set is fixed for that session:

val builder = VpnService.Builder()
builder.addAddress("10.8.0.2", 32)
builder.addRoute("0.0.0.0", 0)
builder.addDisallowedApplication("com.example.bypass")
val tun = builder.establish()   // membership decided here

Those calls apply to the tunnel your app just created and to nothing else. They are not a device-wide policy, they do not persist for other clients, and they cannot reach into somebody else's VPN. When a builder call throws because the package cannot be resolved, the interface is never established, which is a clean failure rather than a silent one.

Why the exclusion looked like a block

A disallowed application is routed around your tunnel onto the underlying network. That is the opposite of blocking it: the traffic still leaves the device, just outside the interface you control. A firewall-style app does the reverse. It admits the application's traffic into its tunnel and then discards the packets, or refuses to forward them, so the interface is the same and the policy direction is inverted.

The work behind that inversion is not a configuration flag. You read IP packets from the tunnel descriptor, decide per flow, and either forward them through a protected socket or drop them. Anything you fail to handle simply stops, which is why a half-finished tunnel looks like a broken network rather than a working filter. If your goal is a block, budget for packet handling, a foreground service and a written policy for traffic you cannot classify.

What breaks when you build it yourself

Android generally permits one active VPN at a time, so your filtering tunnel and a privacy tunnel compete for the same slot unless the products support chaining. Always-on VPN with block-connections changes the failure mode in a way that matters here: traffic that cannot enter the tunnel is dropped instead of being sent directly, which is what a leak-prevention design wants and what a debugging session rarely expects.

Package visibility is the second trap. Resolving an application's package name can fail with NameNotFoundException, and on newer Android versions the set of packages you can see depends on the queries you declared. Handle the exception instead of assuming every name resolves, and re-read the list when applications are installed or removed rather than caching it forever.

Managed devices add a third owner. An enterprise mobility policy can set a per-app VPN or an always-on profile, and an ordinary application cannot override it. Check whether the device is managed before debugging your own code, because in that case the list you are trying to change may not be editable by anyone holding the phone.

Test it with an app you control

Do not test with the app you are trying to filter. Use a second application whose network behaviour you can observe, and check whether its traffic leaves through your tunnel while the target's does not. If both stay inside, the policy is working and the target is excluded; if neither leaves, the tunnel itself is wrong and the app list is not the problem.

Then decide which of three things you are actually building. A per-app tunnel is the builder API, with the list as part of your profile. A device firewall is packet handling plus a lifecycle you can defend. Changing somebody else's VPN is not a supported path at all, and the honest answer there is that the setting belongs to that product.

ተዛማጅ ጽሑፎች