|
| 1 | +# Design Document |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Refactoring akan menghapus layer abstraksi yang tidak perlu sambil mempertahankan fungsionalitas dan keamanan. Pendekatan utama adalah konsolidasi validasi ke satu layer, simplifikasi Shizuku service dengan mengandalkan built-in security, dan menghapus fitur yang tidak terpakai. |
| 6 | + |
| 7 | +## Architecture Changes |
| 8 | + |
| 9 | +### Before |
| 10 | +``` |
| 11 | +ShellManager → ShellCommandPolicy (validation) |
| 12 | + → ShellService → ShellCommandPolicy (validation again) |
| 13 | + → SafetyValidator (package + injection check) |
| 14 | +
|
| 15 | +ShellService: Session management (UUID, ConcurrentHashMap, TTL) |
| 16 | + : Manual thread management (AtomicReference, thread spawning) |
| 17 | +
|
| 18 | +AppManager → SafetyValidator (package validation) |
| 19 | + → ActionHistoryStore (with rollback metadata) |
| 20 | +``` |
| 21 | + |
| 22 | +### After |
| 23 | +``` |
| 24 | +ShellManager → CommandValidator (single validation layer) |
| 25 | + → ShellService (simple UID check only) |
| 26 | +
|
| 27 | +ShellService: Direct UID validation via Shizuku |
| 28 | + : Simple ProcessBuilder.exec() |
| 29 | +
|
| 30 | +AppManager → SafetyValidator (package safety only) |
| 31 | + → ActionHistoryStore (simple logging) |
| 32 | +``` |
| 33 | + |
| 34 | +## Components and Interfaces |
| 35 | + |
| 36 | +### 1. ShellService Simplification |
| 37 | + |
| 38 | +**Remove:** |
| 39 | +- `openSession()` / `closeSession()` methods |
| 40 | +- Session token parameter from `exec()` and `execReturnCode()` |
| 41 | +- ConcurrentHashMap, UUID, TTL cleanup logic |
| 42 | +- Manual thread spawning for stdout/stderr |
| 43 | +- AtomicReference wrappers |
| 44 | +- Complex stream truncation logic |
| 45 | + |
| 46 | +**Keep:** |
| 47 | +- Direct UID validation via `Binder.getCallingUid()` |
| 48 | +- Timeout handling (15s) |
| 49 | +- Error handling |
| 50 | + |
| 51 | +**New AIDL Interface:** |
| 52 | +```java |
| 53 | +interface IShellService { |
| 54 | + String exec(String command); |
| 55 | + int execReturnCode(String command); |
| 56 | +} |
| 57 | +``` |
| 58 | + |
| 59 | +**Simplified Execution:** |
| 60 | +```kotlin |
| 61 | +private fun executeCommand(command: String): String { |
| 62 | + val process = ProcessBuilder("sh", "-c", command) |
| 63 | + .redirectErrorStream(true) |
| 64 | + .start() |
| 65 | + |
| 66 | + val completed = process.waitFor(15, TimeUnit.SECONDS) |
| 67 | + if (!completed) { |
| 68 | + process.destroyForcibly() |
| 69 | + throw TimeoutException("Command timeout") |
| 70 | + } |
| 71 | + |
| 72 | + val output = process.inputStream.bufferedReader().readText() |
| 73 | + if (process.exitValue() != 0) { |
| 74 | + throw Exception(output.ifEmpty { "Exit code: ${process.exitValue()}" }) |
| 75 | + } |
| 76 | + return output.trim() |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +### 2. Command Validation Consolidation |
| 81 | + |
| 82 | +**Create New:** `CommandValidator` in `core/` package |
| 83 | + |
| 84 | +**Responsibilities:** |
| 85 | +- Validate command syntax (forbidden chars, length, shell patterns) |
| 86 | +- Validate command whitelist (pm, am, appops, cmd, dumpsys) |
| 87 | +- Single source of truth for command validation |
| 88 | + |
| 89 | +**Remove:** |
| 90 | +- `ShellCommandPolicy` class entirely |
| 91 | +- Command validation from `ShellService` |
| 92 | +- Injection detection from `SafetyValidator` |
| 93 | + |
| 94 | +**Integration Point:** |
| 95 | +- Called only in `ShellManager.execute()` before sending to service |
| 96 | + |
| 97 | +### 3. SafetyValidator Simplification |
| 98 | + |
| 99 | +**Keep Only:** |
| 100 | +- Package name format validation |
| 101 | +- Safety level determination (CRITICAL, FORCE_STOP_ONLY, WARNING, SAFE) |
| 102 | +- Allowed actions per safety level |
| 103 | + |
| 104 | +**Remove:** |
| 105 | +- Injection character detection (handled by CommandValidator) |
| 106 | +- Excessive hardcoded package lists |
| 107 | + |
| 108 | +**Reduced Critical Packages (25 items):** |
| 109 | +```kotlin |
| 110 | +private val CRITICAL_PACKAGES = setOf( |
| 111 | + // Self |
| 112 | + "com.appcontrolx", |
| 113 | + |
| 114 | + // Core Android |
| 115 | + "android", |
| 116 | + "com.android.systemui", |
| 117 | + "com.android.settings", |
| 118 | + "com.android.phone", |
| 119 | + "com.android.shell", |
| 120 | + "com.android.bluetooth", |
| 121 | + "com.android.wifi", |
| 122 | + "com.android.networkstack", |
| 123 | + "com.android.permissioncontroller", |
| 124 | + "com.android.packageinstaller", |
| 125 | + "com.android.webview", |
| 126 | + |
| 127 | + // Google Core |
| 128 | + "com.google.android.gms", |
| 129 | + "com.google.android.gsf", |
| 130 | + "com.android.vending", |
| 131 | + |
| 132 | + // Vendor Launchers (critical only) |
| 133 | + "com.android.launcher3", |
| 134 | + "com.miui.home", |
| 135 | + "com.sec.android.app.launcher", |
| 136 | + "com.oppo.launcher", |
| 137 | + "com.huawei.android.launcher", |
| 138 | + |
| 139 | + // Root/Shizuku |
| 140 | + "com.topjohnwu.magisk", |
| 141 | + "rikka.shizuku", |
| 142 | + "moe.shizuku.privileged.api", |
| 143 | + "me.weishu.kernelsu" |
| 144 | +) |
| 145 | +``` |
| 146 | + |
| 147 | +**Force-Stop Only (5 items):** |
| 148 | +```kotlin |
| 149 | +private val FORCE_STOP_ONLY_PACKAGES = setOf( |
| 150 | + "com.miui.powerkeeper", |
| 151 | + "com.samsung.android.lool", |
| 152 | + "com.coloros.safecenter", |
| 153 | + "com.huawei.systemmanager", |
| 154 | + "com.google.android.apps.adm" |
| 155 | +) |
| 156 | +``` |
| 157 | + |
| 158 | +### 4. ShellManager Updates |
| 159 | + |
| 160 | +**Changes:** |
| 161 | +- Remove session token management |
| 162 | +- Add CommandValidator call before execution |
| 163 | +- Simplify Shizuku service binding (no session open/close) |
| 164 | +- Update `executeViaShizuku()` to call new AIDL interface |
| 165 | + |
| 166 | +**Flow:** |
| 167 | +```kotlin |
| 168 | +suspend fun execute(command: String): Result<String> { |
| 169 | + // Single validation point |
| 170 | + CommandValidator.validate(command).getOrElse { |
| 171 | + return Result.failure(it) |
| 172 | + } |
| 173 | + |
| 174 | + when (currentMode) { |
| 175 | + ExecutionMode.ROOT -> executeViaRoot(command) |
| 176 | + ExecutionMode.SHIZUKU -> executeViaShizuku(command) |
| 177 | + ExecutionMode.NONE -> Result.failure(...) |
| 178 | + } |
| 179 | +} |
| 180 | + |
| 181 | +private fun executeViaShizuku(command: String): Result<String> { |
| 182 | + val service = getShizukuService() ?: return Result.failure(...) |
| 183 | + return try { |
| 184 | + val output = service.exec(command) // No token needed |
| 185 | + Result.success(output) |
| 186 | + } catch (e: Exception) { |
| 187 | + Result.failure(e) |
| 188 | + } |
| 189 | +} |
| 190 | +``` |
| 191 | + |
| 192 | +### 5. ActionHistory Simplification |
| 193 | + |
| 194 | +**Remove from ActionHistoryItem:** |
| 195 | +- `canRollback: Boolean` field |
| 196 | + |
| 197 | +**Remove from AppManager:** |
| 198 | +- `isRollbackSupported()` method |
| 199 | +- Rollback logic in `executeAction()` |
| 200 | + |
| 201 | +**Simplified History Storage:** |
| 202 | +```kotlin |
| 203 | +actionHistoryStore.addAction( |
| 204 | + ActionHistoryItem( |
| 205 | + packageName = packageName, |
| 206 | + appName = resolveAppName(packageName), |
| 207 | + action = action, |
| 208 | + timestamp = System.currentTimeMillis() |
| 209 | + ) |
| 210 | +) |
| 211 | +``` |
| 212 | + |
| 213 | +## Data Models |
| 214 | + |
| 215 | +### Modified Models |
| 216 | + |
| 217 | +**ActionHistoryItem:** |
| 218 | +```kotlin |
| 219 | +@Serializable |
| 220 | +data class ActionHistoryItem( |
| 221 | + val packageName: String, |
| 222 | + val appName: String, |
| 223 | + val action: AppAction, |
| 224 | + val timestamp: Long |
| 225 | +) |
| 226 | +``` |
| 227 | + |
| 228 | +**IShellService.aidl:** |
| 229 | +```java |
| 230 | +interface IShellService { |
| 231 | + String exec(String command); |
| 232 | + int execReturnCode(String command); |
| 233 | +} |
| 234 | +``` |
| 235 | + |
| 236 | +## Error Handling |
| 237 | + |
| 238 | +**Maintained:** |
| 239 | +- Timeout handling (15s for commands) |
| 240 | +- Exit code validation |
| 241 | +- Exception propagation |
| 242 | +- Security exceptions for unauthorized operations |
| 243 | + |
| 244 | +**Simplified:** |
| 245 | +- No session expiry errors |
| 246 | +- No token mismatch errors |
| 247 | +- Direct error messages from command execution |
| 248 | + |
| 249 | +## Testing Strategy |
| 250 | + |
| 251 | +**Validation:** |
| 252 | +- Build project without errors |
| 253 | +- Verify lint passes |
| 254 | +- Test all app actions (freeze, unfreeze, force-stop, etc.) |
| 255 | +- Verify security validation still blocks dangerous operations |
| 256 | + |
| 257 | +**Manual Testing:** |
| 258 | +- Test with ROOT mode |
| 259 | +- Test with SHIZUKU mode |
| 260 | +- Verify action history still logs correctly |
| 261 | +- Confirm UI remains unchanged |
| 262 | + |
| 263 | +## Migration Notes |
| 264 | + |
| 265 | +**Breaking Changes:** |
| 266 | +- AIDL interface changed (Shizuku service needs rebuild) |
| 267 | +- ActionHistoryItem serialization format changed (old history incompatible) |
| 268 | + |
| 269 | +**Non-Breaking:** |
| 270 | +- All app functionality remains identical |
| 271 | +- UI unchanged |
| 272 | +- User experience unchanged |
0 commit comments