Claude
Skills
Sign in
Back

anubis-android-app-manager

Included with Lifetime
$97 forever

Android app manager with VPN integration that freezes/unfreezes app groups based on VPN connection state using Shizuku's pm disable-user

General

What this skill does


# Anubis Android App Manager

> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection.

Anubis is an Android app manager that uses Shizuku to completely disable (`pm disable-user`) app groups based on VPN connection state. Unlike sandboxing solutions (Island, Shelter), disabled apps cannot run code, access network interfaces, or detect VPN presence at all.

## Core Concepts

| Group Policy | Behavior |
|---|---|
| **Local** | Frozen when VPN is active; runs without VPN |
| **VPN Only** | Frozen when VPN is inactive; runs through VPN |
| **Launch with VPN** | Never frozen; launching triggers VPN activation |

## Requirements

- Android 10+ (API 29)
- [Shizuku](https://shizuku.rikka.app/) installed and running
- At least one VPN client installed

## Setup

1. Install and start Shizuku (via ADB or Wireless Debugging):
   ```bash
   adb shell sh /sdcard/Android/data/moe.shizuku.privileged.api/start.sh
   ```
2. Install Anubis APK
3. Grant Shizuku permission when prompted
4. Grant VPN permission when prompted (needed for dummy VPN disconnect)
5. Go to **Apps** tab → assign apps to groups
6. Select VPN client in **Settings**
7. Toggle stealth mode on **Home** screen

## Building

```bash
git clone https://github.com/sogonov/anubis
cd anubis

# Debug build
./gradlew assembleDebug

# Release build (requires signing config)
./gradlew assembleRelease
```

Create `signing.properties` in project root for release builds:
```properties
storeFile=release.keystore
storePassword=${KEYSTORE_PASSWORD}
keyAlias=${KEY_ALIAS}
keyPassword=${KEY_PASSWORD}
```

## Tech Stack

- Kotlin + Jetpack Compose (Material 3)
- Shizuku API 13.1.5 (AIDL UserService pattern)
- Room database with TypeConverters for app groups
- `ConnectivityManager` NetworkCallback for VPN state monitoring
- `ShortcutManager` for pinned shortcuts

## Project Structure

```
app/src/main/java/
├── data/
│   ├── AppGroup.kt          # Room entity: group name, policy, package list
│   ├── AppGroupDao.kt       # DAO: CRUD for app groups
│   └── AppDatabase.kt       # Room database setup
├── service/
│   ├── ShizukuService.kt    # AIDL UserService: pm/am shell commands
│   ├── VpnStateMonitor.kt   # ConnectivityManager NetworkCallback
│   └── FreezeManager.kt     # Orchestrates freeze/unfreeze logic
├── vpn/
│   ├── VpnClient.kt         # Enum: SEPARATE, TOGGLE, MANUAL
│   └── VpnClientRegistry.kt # Known clients + custom client support
└── ui/
    ├── HomeScreen.kt         # Launcher grid, VPN toggle
    ├── AppsScreen.kt         # Group assignment UI
    └── SettingsScreen.kt     # VPN client selection
```

## Room Database: App Groups

```kotlin
@Entity(tableName = "app_groups")
data class AppGroup(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val name: String,
    val policy: GroupPolicy,
    val packages: List<String> // stored via TypeConverter
)

enum class GroupPolicy {
    LOCAL,        // frozen when VPN active
    VPN_ONLY,     // frozen when VPN inactive
    LAUNCH_WITH_VPN // never frozen, VPN triggered on launch
}
```

```kotlin
@Dao
interface AppGroupDao {
    @Query("SELECT * FROM app_groups")
    fun getAllGroups(): Flow<List<AppGroup>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertGroup(group: AppGroup)

    @Delete
    suspend fun deleteGroup(group: AppGroup)

    @Update
    suspend fun updateGroup(group: AppGroup)
}
```

## Shizuku Integration (AIDL UserService Pattern)

Define the AIDL interface:
```kotlin
// IShizukuService.aidl
interface IShizukuService {
    String executeCommand(String command);
    void destroy();
}
```

Implement the UserService:
```kotlin
class ShizukuUserService : IShizukuService.Stub() {
    override fun executeCommand(command: String): String {
        return try {
            val process = Runtime.getRuntime().exec(arrayOf("sh", "-c", command))
            process.inputStream.bufferedReader().readText()
        } catch (e: Exception) {
            "ERROR: ${e.message}"
        }
    }

    override fun destroy() {
        exitProcess(0)
    }
}
```

Bind to the UserService:
```kotlin
class FreezeManager(private val context: Context) {

    private var service: IShizukuService? = null

    private val serviceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, binder: IBinder) {
            service = IShizukuService.Stub.asInterface(binder)
        }
        override fun onServiceDisconnected(name: ComponentName) {
            service = null
        }
    }

    private val userServiceArgs = Shizuku.UserServiceArgs(
        ComponentName(context, ShizukuUserService::class.java)
    ).processNameSuffix("service").debuggable(false).version(1)

    fun bindService() {
        if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
            Shizuku.bindUserService(userServiceArgs, serviceConnection)
        }
    }

    fun unbindService() {
        Shizuku.unbindUserService(userServiceArgs, serviceConnection, true)
    }

    suspend fun freezePackage(packageName: String) {
        service?.executeCommand("pm disable-user --user 0 $packageName")
    }

    suspend fun unfreezePackage(packageName: String) {
        service?.executeCommand("pm enable --user 0 $packageName")
    }
}
```

## VPN State Monitoring

```kotlin
class VpnStateMonitor(private val context: Context) {

    private val connectivityManager =
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    private val _isVpnActive = MutableStateFlow(false)
    val isVpnActive: StateFlow<Boolean> = _isVpnActive.asStateFlow()

    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            val capabilities = connectivityManager.getNetworkCapabilities(network)
            if (capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true) {
                _isVpnActive.value = true
            }
        }

        override fun onLost(network: Network) {
            // Re-check if any VPN network remains
            val hasVpn = connectivityManager.allNetworks.any { net ->
                connectivityManager.getNetworkCapabilities(net)
                    ?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true
            }
            _isVpnActive.value = hasVpn
        }
    }

    fun startMonitoring() {
        val request = NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
            .build()
        connectivityManager.registerNetworkCallback(request, networkCallback)
    }

    fun stopMonitoring() {
        connectivityManager.unregisterNetworkCallback(networkCallback)
    }
}
```

## VPN Client Control

```kotlin
enum class VpnControlMethod { SEPARATE, TOGGLE, MANUAL }

data class VpnClientConfig(
    val packageName: String,
    val method: VpnControlMethod,
    val startAction: String? = null,
    val stopAction: String? = null,
    val toggleAction: String? = null,
    val receiverClass: String? = null
)

val KNOWN_VPN_CLIENTS = mapOf(
    "com.v2ray.ang" to VpnClientConfig(
        packageName = "com.v2ray.ang",
        method = VpnControlMethod.TOGGLE,
        toggleAction = "com.v2ray.ang.action.widget.click",
        receiverClass = "com.v2ray.ang.receiver.WidgetProvider"
    ),
    "moe.nb4a" to VpnClientConfig(
        packageName = "moe.nb4a",
        method = VpnControlMethod.SEPARATE,
        startAction = "moe.nb4a.ui.QuickEnable",
        stopAction = "moe.nb4a.ui.QuickDisable"
    ),
    "com.happproxy" to VpnClientConfig(
        packageName = "com.happproxy",
        method = VpnControlMethod.TOGGLE,
        toggleAction = "com.happproxy.action.widget.click",
        receiverClass = "com.happproxy.receiver.WidgetProvider"
    )
)
```

Start/stop VPN via shell:
```kotlin
suspend fun startVpn(config: VpnClientConfig) {
    when (config.method) {
        VpnControlMethod.TOGGLE, VpnControlMethod.S

Related in General