diff --git a/apk/controller/app/src/main/AndroidManifest.xml b/apk/controller/app/src/main/AndroidManifest.xml
index 0a5fc31..c46df93 100644
--- a/apk/controller/app/src/main/AndroidManifest.xml
+++ b/apk/controller/app/src/main/AndroidManifest.xml
@@ -66,6 +66,7 @@
+
diff --git a/apk/controller/app/src/main/java/org/iiab/controller/MainActivity.java b/apk/controller/app/src/main/java/org/iiab/controller/MainActivity.java
index 6bdb25d..d902e60 100644
--- a/apk/controller/app/src/main/java/org/iiab/controller/MainActivity.java
+++ b/apk/controller/app/src/main/java/org/iiab/controller/MainActivity.java
@@ -101,6 +101,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
private TextView logWarning;
private TextView logSizeText;
private ImageButton themeToggle;
+ private ImageButton btnSettings;
private TextView versionFooter;
private ProgressBar logProgress;
@@ -134,6 +135,14 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ // Intercept launch and redirect to Setup Wizard if first time
+ SharedPreferences internalPrefs = getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
+ if (!internalPrefs.getBoolean("setup_complete", false)) {
+ startActivity(new Intent(this, SetupActivity.class));
+ finish();
+ return;
+ }
+
prefs = new Preferences(this);
setContentView(R.layout.main);
@@ -200,6 +209,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
logWarning = findViewById(R.id.log_warning_text);
logSizeText = findViewById(R.id.log_size_text);
themeToggle = findViewById(R.id.theme_toggle);
+ btnSettings = findViewById(R.id.btn_settings);
versionFooter = findViewById(R.id.version_text);
configLayout = findViewById(R.id.config_layout);
configLabel = findViewById(R.id.config_label);
@@ -212,6 +222,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
btnClearLog.setOnClickListener(this);
btnCopyLog.setOnClickListener(this);
themeToggle.setOnClickListener(v -> toggleTheme());
+ btnSettings.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, SetupActivity.class)));
configLabel.setOnClickListener(v -> handleConfigToggle());
advConfigLabel.setOnClickListener(v -> toggleVisibility(advancedConfig, advConfigLabel, getString(R.string.advanced_settings_label)));
logLabel.setOnClickListener(v -> handleLogToggle());
@@ -241,7 +252,6 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
applySavedTheme();
setVersionFooter();
updateUI();
- initiatePermissionChain();
addToLog(getString(R.string.app_started));
diff --git a/apk/controller/app/src/main/java/org/iiab/controller/SetupActivity.java b/apk/controller/app/src/main/java/org/iiab/controller/SetupActivity.java
new file mode 100644
index 0000000..6ea6d4a
--- /dev/null
+++ b/apk/controller/app/src/main/java/org/iiab/controller/SetupActivity.java
@@ -0,0 +1,179 @@
+package org.iiab.controller;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.net.VpnService;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.SwitchCompat;
+import androidx.core.content.ContextCompat;
+
+public class SetupActivity extends AppCompatActivity {
+
+ private static final String TERMUX_PERMISSION = "com.termux.permission.RUN_COMMAND";
+
+ private SwitchCompat switchNotif, switchTermux, switchVpn, switchBattery;
+ private Button btnContinue;
+
+ private ActivityResultLauncher requestPermissionLauncher;
+ private ActivityResultLauncher vpnLauncher;
+ private ActivityResultLauncher batteryLauncher;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_setup);
+
+ TextView welcomeText = findViewById(R.id.setup_welcome_text);
+ welcomeText.setText(getString(R.string.setup_welcome, getString(R.string.app_name)));
+
+ switchNotif = findViewById(R.id.switch_perm_notifications);
+ switchTermux = findViewById(R.id.switch_perm_termux);
+ switchVpn = findViewById(R.id.switch_perm_vpn);
+ switchBattery = findViewById(R.id.switch_perm_battery);
+ btnContinue = findViewById(R.id.btn_setup_continue);
+
+ // Hide Notification switch if Android < 13
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ switchNotif.setVisibility(android.view.View.GONE);
+ }
+
+ setupLaunchers();
+ setupListeners();
+ checkAllPermissions();
+
+ btnContinue.setOnClickListener(v -> {
+ // Save flag so we don't show this screen again
+ SharedPreferences prefs = getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
+ prefs.edit().putBoolean("setup_complete", true).apply();
+
+ finish();
+ });
+ }
+
+ private void setupLaunchers() {
+ requestPermissionLauncher = registerForActivityResult(
+ new ActivityResultContracts.RequestPermission(),
+ isGranted -> checkAllPermissions()
+ );
+
+ vpnLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> checkAllPermissions()
+ );
+
+ batteryLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> checkAllPermissions()
+ );
+ }
+
+ private void setupListeners() {
+ switchNotif.setOnClickListener(v -> {
+ if (switchNotif.isChecked()) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
+ }
+ }
+ switchNotif.setChecked(hasNotifPermission()); // Revert visual state pending system response
+ });
+
+ switchTermux.setOnClickListener(v -> {
+ if (switchTermux.isChecked()) {
+ requestPermissionLauncher.launch(TERMUX_PERMISSION);
+ }
+ switchTermux.setChecked(hasTermuxPermission());
+ });
+
+ switchVpn.setOnClickListener(v -> {
+ if (switchVpn.isChecked()) {
+ Intent intent = VpnService.prepare(this);
+ if (intent != null) {
+ vpnLauncher.launch(intent);
+ } else {
+ checkAllPermissions(); // Already granted
+ }
+ }
+ switchVpn.setChecked(hasVpnPermission());
+ });
+
+ switchBattery.setOnClickListener(v -> {
+ if (switchBattery.isChecked()) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
+ intent.setData(Uri.parse("package:" + getPackageName()));
+ batteryLauncher.launch(intent);
+ }
+ }
+ switchBattery.setChecked(hasBatteryPermission());
+ });
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ checkAllPermissions(); // Refresh state if user returns from settings
+ }
+
+ private void checkAllPermissions() {
+ boolean notif = hasNotifPermission();
+ boolean termux = hasTermuxPermission();
+ boolean vpn = hasVpnPermission();
+ boolean battery = hasBatteryPermission();
+
+ switchNotif.setChecked(notif);
+ switchTermux.setChecked(termux);
+ switchVpn.setChecked(vpn);
+ switchBattery.setChecked(battery);
+
+ // Lock switches if already granted to prevent user confusion
+ if (notif) switchNotif.setEnabled(false);
+ if (termux) switchTermux.setEnabled(false);
+ if (vpn) switchVpn.setEnabled(false);
+ if (battery) switchBattery.setEnabled(false);
+
+ boolean allGranted = termux && vpn && battery;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ allGranted = allGranted && notif;
+ }
+
+ btnContinue.setEnabled(allGranted);
+ btnContinue.setBackgroundTintList(ContextCompat.getColorStateList(this,
+ allGranted ? R.color.btn_explore_ready : R.color.btn_explore_disabled));
+ }
+
+ private boolean hasNotifPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ return ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
+ }
+ return true;
+ }
+
+ private boolean hasTermuxPermission() {
+ return ContextCompat.checkSelfPermission(this, TERMUX_PERMISSION) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean hasVpnPermission() {
+ return VpnService.prepare(this) == null;
+ }
+
+ private boolean hasBatteryPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ return pm != null && pm.isIgnoringBatteryOptimizations(getPackageName());
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/apk/controller/app/src/main/res/layout/activity_setup.xml b/apk/controller/app/src/main/res/layout/activity_setup.xml
new file mode 100644
index 0000000..7dfa601
--- /dev/null
+++ b/apk/controller/app/src/main/res/layout/activity_setup.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apk/controller/app/src/main/res/layout/main.xml b/apk/controller/app/src/main/res/layout/main.xml
index 2745a9a..876b8a2 100644
--- a/apk/controller/app/src/main/res/layout/main.xml
+++ b/apk/controller/app/src/main/res/layout/main.xml
@@ -31,6 +31,18 @@
android:textSize="18sp"
android:textStyle="bold" />
+
+
+ Initial Setup
+ Welcome to the %1$s setup wizard.\n\nIn order to work properly, we need the following permissions:
+ Push Notifications
+ Termux Execution
+ Safe Pocket Web (VPN)
+ Disable Battery Optimization
+ Continue
IIAB-oA Controller
Dirección Socks:
Dirección UDP Socks:
@@ -31,7 +38,7 @@
Copiar Todo
Sistema listo...\n
Configuración
- Ajustes Avanzados
+ Ajustes del Túnel
Log de Conexión
Aplicación Iniciada
--- No se encontró el archivo BlackBox ---
diff --git a/apk/controller/app/src/main/res/values-ru-rRU/strings.xml b/apk/controller/app/src/main/res/values-ru-rRU/strings.xml
index d65f3ec..eb55e51 100644
--- a/apk/controller/app/src/main/res/values-ru-rRU/strings.xml
+++ b/apk/controller/app/src/main/res/values-ru-rRU/strings.xml
@@ -1,20 +1,93 @@
- SocksTun
- Адрес Socks:
- UDP-aдрес Socks:
- Порт Socks:
- Имя пользователя Socks:
- Пароль Socks:
+ Initial Setup
+ Welcome to the %1$s setup wizard.\n\nIn order to work properly, we need the following permissions:
+ Push Notifications
+ Termux Execution
+ Safe Pocket Web (VPN)
+ Disable Battery Optimization
+ Continue
+ IIAB-oA Controller
+ Socks Address:
+ Socks UDP Address:
+ Socks Port:
+ Socks Username:
+ Socks Password:
DNS IPv4:
DNS IPv6:
- UDP через TCP
- Удалённый DNS
+ UDP relay over TCP
+ Remote DNS
IPv4
IPv6
- Глобально
- Приложения
- Сохранить
- Включить
- Отключить
-
+ Global
+ Apps
+ Save
+ Enable Safe Pocket Web
+ Disable Safe Pocket Web
+ Enable friendly URLs. Lock out the threats.
+ Enable Master Watchdog
+ Disable Master Watchdog
+ Reset Log History?
+ This will permanently delete all stored connection logs. This action cannot be undone.
+ The logging file is growing too rapidly, you might want to check if something is failing
+
+
+ 🚀 Explore Content
+ Protects Termux from Doze mode and keeps Wi-Fi active.
+ Reset Log
+ Copy All
+ System ready...\n
+ Configuration
+ Tunnel Settings
+ Connection Log
+ Application Started
+ --- No BlackBox file found ---
+ --- Loading History ---
+ Error reading history: %s
+ --- End of History ---
+ Recovery Pulse Received from System. Enforcing VPN...
+ Battery Optimization
+ For the Watchdog to work reliably, please disable battery optimizations for this app.
+ Go to Settings
+ Cancel
+ Saved
+ Settings Saved
+ Log reset
+ Log reset by user
+ Log copied to clipboard
+ Watchdog Stopped
+ Watchdog Started
+ VPN Stopping...
+ VPN Starting...
+ Log cleared
+ Failed to reset log: %s
+ Unlock Master Watchdog
+ Authentication required to stop Termux protection
+ Authentication Success. Disconnecting...
+ Authentication required
+ Authenticate to disable the secure environment
+ Security Required
+ You must set up a PIN, Pattern, or Fingerprint on your device before enabling the secure environment.
+ User initiated connection
+ VPN Permission Granted. Connecting...
+
+
+ Pulse: Stimulating Termux...
+ CRITICAL: OS blocked Termux stimulus (SecurityException).
+ PING 8085: OK
+ PING 8085: FAIL (%s)
+ HEARTBEAT SESSION STARTED
+ HEARTBEAT SESSION STOPPED
+
+
+ [Termux] Stimulus OK (exit 0)
+ [Termux] Pulse Error (exit %1$d): %2$s
+
+ Size: %1$s / 10MB
+
+
+ \n\nOPPO/Realme detected: Please ensure you also enable \'Allow background activity\' in this app\'s settings.
+ \n\nXiaomi detected: Please set battery saver to \'No restrictions\' in settings.
+ For the app to work 100%, please disable battery optimization.
+ FIX
+
\ No newline at end of file
diff --git a/apk/controller/app/src/main/res/values/strings.xml b/apk/controller/app/src/main/res/values/strings.xml
index 820d68f..509587e 100644
--- a/apk/controller/app/src/main/res/values/strings.xml
+++ b/apk/controller/app/src/main/res/values/strings.xml
@@ -1,5 +1,12 @@
+ Initial Setup
+ Welcome to the %1$s setup wizard.\n\nIn order to work properly, we need the following permissions:
+ Push Notifications
+ Termux Execution
+ Safe Pocket Web (VPN)
+ Disable Battery Optimization
+ Continue
IIAB-oA Controller
Socks Address:
Socks UDP Address:
@@ -31,7 +38,7 @@
Copy All
System ready...\n
Configuration
- Advanced Settings
+ Tunnel Settings
Connection Log
Application Started
--- No BlackBox file found ---