[controller] rediseño mayor de panel de control, sincronización de estados, y acciones.
This commit is contained in:
parent
1393a288c7
commit
5eac64be96
|
|
@ -73,6 +73,8 @@
|
|||
</application>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
package org.iiab.controller;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class BatteryUtils {
|
||||
|
||||
// Previously at MainActivity
|
||||
public static void checkAndPromptOptimizations(Activity activity, ActivityResultLauncher<Intent> launcher) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PowerManager pm = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
|
||||
if (pm != null && !pm.isIgnoringBatteryOptimizations(activity.getPackageName())) {
|
||||
String manufacturer = Build.MANUFACTURER.toLowerCase();
|
||||
String message = activity.getString(R.string.battery_opt_msg);
|
||||
|
||||
if (manufacturer.contains("oppo") || manufacturer.contains("realme") || manufacturer.contains("xiaomi")) {
|
||||
|
||||
if (manufacturer.contains("oppo") || manufacturer.contains("realme")) {
|
||||
message += activity.getString(R.string.battery_opt_oppo_extra);
|
||||
} else if (manufacturer.contains("xiaomi")) {
|
||||
message += activity.getString(R.string.battery_opt_xiaomi_extra);
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.battery_opt_title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.go_to_settings, (dialog, which) -> openBatterySettings(activity, manufacturer))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
} else {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.parse("package:" + activity.getPackageName()));
|
||||
launcher.launch(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void openBatterySettings(Activity activity, String manufacturer) {
|
||||
boolean success = false;
|
||||
String packageName = activity.getPackageName();
|
||||
|
||||
if (manufacturer.contains("oppo") || manufacturer.contains("realme")) {
|
||||
try {
|
||||
Intent intent = new Intent();
|
||||
intent.setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity"));
|
||||
activity.startActivity(intent);
|
||||
success = true;
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
Intent intent = new Intent();
|
||||
intent.setComponent(new ComponentName("com.coloros.oppoguardelf", "com.coloros.oppoguardelf.Permission.BackgroundAllowAppListActivity"));
|
||||
activity.startActivity(intent);
|
||||
success = true;
|
||||
} catch (Exception e2) {}
|
||||
}
|
||||
} else if (manufacturer.contains("xiaomi")) {
|
||||
try {
|
||||
Intent intent = new Intent("miui.intent.action.APP_BATTERY_SAVER_SETTINGS");
|
||||
intent.setComponent(new ComponentName("com.miui.powerkeeper", "com.miui.powerkeeper.ui.HiddenAppsConfigActivity"));
|
||||
intent.putExtra("package_name", packageName);
|
||||
intent.putExtra("package_label", activity.getString(R.string.app_name));
|
||||
activity.startActivity(intent);
|
||||
success = true;
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
intent.setData(Uri.parse("package:" + packageName));
|
||||
activity.startActivity(intent);
|
||||
} catch (Exception ex) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package org.iiab.controller;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.biometric.BiometricManager;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class BiometricHelper {
|
||||
|
||||
// This is the "phone line" that tells MainActivity the user succeeded
|
||||
public interface AuthCallback {
|
||||
void onSuccess();
|
||||
}
|
||||
|
||||
public static boolean isDeviceSecure(Context context) {
|
||||
BiometricManager bm = BiometricManager.from(context);
|
||||
int auth = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
auth = BiometricManager.Authenticators.BIOMETRIC_WEAK | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
}
|
||||
android.app.KeyguardManager km = (android.app.KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
|
||||
|
||||
return bm.canAuthenticate(auth) == BiometricManager.BIOMETRIC_SUCCESS || (km != null && km.isDeviceSecure());
|
||||
}
|
||||
|
||||
public static void showEnrollmentDialog(Context context) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.security_required_title)
|
||||
.setMessage(R.string.security_required_msg)
|
||||
.setPositiveButton(R.string.go_to_settings, (dialog, which) -> {
|
||||
Intent intent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
|
||||
context.startActivity(intent);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
public static void prompt(AppCompatActivity activity, String title, String subtitle, AuthCallback callback) {
|
||||
Executor executor = ContextCompat.getMainExecutor(activity);
|
||||
BiometricPrompt biometricPrompt = new BiometricPrompt(activity, executor, new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
// Call back to MainActivity!
|
||||
callback.onSuccess();
|
||||
}
|
||||
});
|
||||
|
||||
int auth = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
auth = BiometricManager.Authenticators.BIOMETRIC_WEAK | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
}
|
||||
|
||||
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(title)
|
||||
.setSubtitle(subtitle)
|
||||
.setAllowedAuthenticators(auth)
|
||||
.build();
|
||||
|
||||
biometricPrompt.authenticate(promptInfo);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package org.iiab.controller;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.provider.Settings;
|
||||
import android.transition.AutoTransition;
|
||||
import android.transition.TransitionManager;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
public class DashboardManager {
|
||||
|
||||
private final Activity activity;
|
||||
private final LinearLayout dashboardContainer;
|
||||
|
||||
private final View dashWifi, dashHotspot, dashTunnel;
|
||||
private final View ledWifi, ledHotspot, ledTunnel;
|
||||
private final View standaloneEspwButton;
|
||||
private final View standaloneEspwDescription;
|
||||
|
||||
// We pass a Callback so the Dashboard can tell MainActivity to start/stop the VPN
|
||||
public interface DashboardActionCallback {
|
||||
void onToggleEspwRequested();
|
||||
}
|
||||
|
||||
public DashboardManager(Activity activity, View rootView, DashboardActionCallback callback) {
|
||||
this.activity = activity;
|
||||
|
||||
// Bind all the views
|
||||
dashboardContainer = (LinearLayout) rootView.findViewById(R.id.dashboard_container);
|
||||
dashWifi = rootView.findViewById(R.id.dash_wifi);
|
||||
dashHotspot = rootView.findViewById(R.id.dash_hotspot);
|
||||
dashTunnel = rootView.findViewById(R.id.dash_tunnel);
|
||||
|
||||
ledWifi = rootView.findViewById(R.id.led_wifi);
|
||||
ledHotspot = rootView.findViewById(R.id.led_hotspot);
|
||||
ledTunnel = rootView.findViewById(R.id.led_tunnel);
|
||||
|
||||
standaloneEspwButton = rootView.findViewById(R.id.control);
|
||||
standaloneEspwDescription = rootView.findViewById(R.id.control_description);
|
||||
|
||||
setupListeners(callback);
|
||||
}
|
||||
|
||||
private void setupListeners(DashboardActionCallback callback) {
|
||||
// Single tap opens Settings directly (No wrench icons needed!)
|
||||
dashWifi.setOnClickListener(v -> activity.startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)));
|
||||
|
||||
dashHotspot.setOnClickListener(v -> {
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
|
||||
activity.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
activity.startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
|
||||
}
|
||||
});
|
||||
|
||||
// The Tunnel/ESPW toggle logic
|
||||
View.OnClickListener toggleEspw = v -> callback.onToggleEspwRequested();
|
||||
standaloneEspwButton.setOnClickListener(toggleEspw);
|
||||
dashTunnel.setOnClickListener(toggleEspw);
|
||||
}
|
||||
|
||||
// Updates the LED graphics based on actual OS connectivity states
|
||||
public void updateConnectivityLeds(boolean isWifiOn, boolean isHotspotOn) {
|
||||
ledWifi.setBackgroundResource(isWifiOn ? R.drawable.led_on_green : R.drawable.led_off);
|
||||
ledHotspot.setBackgroundResource(isHotspotOn ? R.drawable.led_on_green : R.drawable.led_off);
|
||||
}
|
||||
|
||||
// The Magic Morphing Animation!
|
||||
public void setTunnelState(boolean isTunnelActive, boolean isDegraded) {
|
||||
// Tells Android to smoothly animate any layout changes we make next
|
||||
TransitionManager.beginDelayedTransition((ViewGroup) dashboardContainer.getParent(), new AutoTransition().setDuration(300));
|
||||
|
||||
if (isTunnelActive) {
|
||||
// Morph into 33% / 33% / 33% Dashboard mode
|
||||
standaloneEspwButton.setVisibility(View.GONE);
|
||||
standaloneEspwDescription.setVisibility(View.GONE);
|
||||
dashTunnel.setVisibility(View.VISIBLE);
|
||||
ledTunnel.setBackgroundResource(isDegraded ? R.drawable.led_on_orange : R.drawable.led_on_green);
|
||||
|
||||
// Force recalculate
|
||||
dashboardContainer.setWeightSum(3f);
|
||||
} else {
|
||||
// Morph back into 50% / 50% mode
|
||||
dashTunnel.setVisibility(View.GONE);
|
||||
standaloneEspwButton.setVisibility(View.VISIBLE);
|
||||
standaloneEspwDescription.setVisibility(View.VISIBLE);
|
||||
// The LED turns off implicitly since the whole dash_tunnel hides, but we can enforce it:
|
||||
ledTunnel.setBackgroundResource(R.drawable.led_off);
|
||||
// Force recalculate
|
||||
dashboardContainer.setWeightSum(2f);
|
||||
}
|
||||
// Force recalculate
|
||||
dashboardContainer.requestLayout();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package org.iiab.controller;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Locale;
|
||||
|
||||
public class LogManager {
|
||||
private static final String LOG_FILE_NAME = "watchdog_heartbeat_log.txt";
|
||||
|
||||
// Callbacks to communicate with MainActivity
|
||||
public interface LogReadCallback {
|
||||
void onResult(String logContent, boolean isRapidGrowth);
|
||||
}
|
||||
|
||||
public interface LogClearCallback {
|
||||
void onSuccess();
|
||||
void onError(String message);
|
||||
}
|
||||
|
||||
// Read the file in the background and return the result to the main thread
|
||||
public static void readLogsAsync(Context context, LogReadCallback callback) {
|
||||
new Thread(() -> {
|
||||
File logFile = new File(context.getFilesDir(), LOG_FILE_NAME);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (!logFile.exists()) {
|
||||
sb.append(context.getString(R.string.no_blackbox_found)).append("\n");
|
||||
} else {
|
||||
sb.append(context.getString(R.string.loading_history)).append("\n");
|
||||
try (BufferedReader br = new BufferedReader(new FileReader(logFile))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
sb.append(line).append("\n");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
sb.append(context.getString(R.string.error_reading_history, e.getMessage())).append("\n");
|
||||
}
|
||||
sb.append(context.getString(R.string.end_of_history)).append("\n");
|
||||
}
|
||||
|
||||
SharedPreferences internalPrefs = context.getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
|
||||
boolean isRapid = internalPrefs.getBoolean(IIABWatchdog.PREF_RAPID_GROWTH, false);
|
||||
String result = sb.toString();
|
||||
|
||||
// We return the call on the main UI thread
|
||||
new Handler(Looper.getMainLooper()).post(() -> callback.onResult(result, isRapid));
|
||||
}).start();
|
||||
}
|
||||
|
||||
// Delete the file securely
|
||||
public static void clearLogs(Context context, LogClearCallback callback) {
|
||||
File logFile = new File(context.getFilesDir(), LOG_FILE_NAME);
|
||||
try (PrintWriter pw = new PrintWriter(logFile)) {
|
||||
pw.print("");
|
||||
context.getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE)
|
||||
.edit().putBoolean(IIABWatchdog.PREF_RAPID_GROWTH, false).apply();
|
||||
callback.onSuccess();
|
||||
} catch (IOException e) {
|
||||
callback.onError(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the file size
|
||||
public static String getFormattedSize(Context context) {
|
||||
File logFile = new File(context.getFilesDir(), LOG_FILE_NAME);
|
||||
long size = logFile.exists() ? logFile.length() : 0;
|
||||
|
||||
if (size < 1024) {
|
||||
return size + " B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
return String.format(Locale.getDefault(), "%.1f KB", size / 1024.0);
|
||||
} else {
|
||||
return String.format(Locale.getDefault(), "%.2f MB", size / (1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,7 +45,10 @@ import android.text.method.ScrollingMovementMethod;
|
|||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.provider.Settings;
|
||||
import android.net.wifi.WifiManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.biometric.BiometricManager;
|
||||
|
|
@ -67,6 +70,8 @@ import java.text.SimpleDateFormat;
|
|||
import java.util.concurrent.Executor;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.Proxy;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
|
||||
private static final String TAG = "IIAB-MainActivity";
|
||||
|
|
@ -105,6 +110,19 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
private TextView versionFooter;
|
||||
private ProgressBar logProgress;
|
||||
|
||||
// Cassette Deck UI
|
||||
private LinearLayout deckContainer;
|
||||
private Button btnServerControl;
|
||||
private ObjectAnimator fusionAnimator;
|
||||
private android.animation.ObjectAnimator exploreAnimator;
|
||||
private boolean isServerAlive = false;
|
||||
private boolean isNegotiating = false;
|
||||
private boolean isProxyDegraded = false;
|
||||
private String currentTargetUrl = null;
|
||||
private long pulseStartTime = 0;
|
||||
|
||||
private DashboardManager dashboardManager;
|
||||
|
||||
private ActivityResultLauncher<Intent> vpnPermissionLauncher;
|
||||
private ActivityResultLauncher<String[]> requestPermissionsLauncher;
|
||||
private ActivityResultLauncher<Intent> batteryOptLauncher;
|
||||
|
|
@ -116,18 +134,44 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
// Variables for adaptive localhost server check
|
||||
private final Handler serverCheckHandler = new Handler(android.os.Looper.getMainLooper());
|
||||
private Runnable serverCheckRunnable;
|
||||
private static final int MIN_CHECK_INTERVAL = 5000; // 5 seconds floor
|
||||
private static final int MAX_CHECK_INTERVAL = 60000; // 60 seconds ceiling
|
||||
private int currentCheckInterval = MIN_CHECK_INTERVAL;
|
||||
private static final int CHECK_INTERVAL_MS = 3000;
|
||||
|
||||
private final BroadcastReceiver logReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (IIABWatchdog.ACTION_LOG_MESSAGE.equals(intent.getAction())) {
|
||||
String action = intent.getAction();
|
||||
|
||||
if (IIABWatchdog.ACTION_LOG_MESSAGE.equals(action)) {
|
||||
String message = intent.getStringExtra(IIABWatchdog.EXTRA_MESSAGE);
|
||||
addToLog(message);
|
||||
updateLogSizeUI();
|
||||
}
|
||||
else if (WatchdogService.ACTION_STATE_STARTED.equals(action)) {
|
||||
// Calculate where we are in the 1200ms cycle (600ms down + 600ms up)
|
||||
long elapsed = System.currentTimeMillis() - pulseStartTime;
|
||||
long fullCycle = 1200;
|
||||
|
||||
// Find out how many milliseconds are left to finish the current wave
|
||||
long remainder = elapsed % fullCycle;
|
||||
long timeToNextCycleEnd = fullCycle - remainder;
|
||||
|
||||
// If the remaining time is too fast (< 1 second), add one more full cycle
|
||||
// so the user actually has time to see the system notification drop down gracefully.
|
||||
if (timeToNextCycleEnd < 1000) {
|
||||
timeToNextCycleEnd += fullCycle;
|
||||
}
|
||||
|
||||
// Wait exactly until the wave hits 1.0f alpha, then lock it!
|
||||
new Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
|
||||
finalizeEntryPulse();
|
||||
}, timeToNextCycleEnd);
|
||||
}
|
||||
else if (WatchdogService.ACTION_STATE_STOPPED.equals(action)) {
|
||||
// Service is down! Give it a 1.5 second visual margin, then stop the exit pulse.
|
||||
new Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
|
||||
finalizeExitPulse();
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -153,7 +197,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
if (result.getResultCode() == RESULT_OK && prefs.getEnable()) {
|
||||
connectVpn();
|
||||
}
|
||||
checkBatteryOptimizations();
|
||||
BatteryUtils.checkAndPromptOptimizations(MainActivity.this, batteryOptLauncher);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -161,7 +205,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
Log.d(TAG, "Returned from the battery settings screen");
|
||||
checkBatteryOptimizations();
|
||||
BatteryUtils.checkAndPromptOptimizations(MainActivity.this, batteryOptLauncher);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -217,8 +261,25 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
advConfigLabel = findViewById(R.id.adv_config_label);
|
||||
logLabel = findViewById(R.id.log_label);
|
||||
|
||||
deckContainer = findViewById(R.id.deck_container);
|
||||
btnServerControl = findViewById(R.id.btn_server_control);
|
||||
|
||||
dashboardManager = new DashboardManager(this, findViewById(android.R.id.content), () -> {
|
||||
handleControlClick();
|
||||
});
|
||||
|
||||
// Listeners
|
||||
watchdogControl.setOnClickListener(this);
|
||||
watchdogControl.setOnClickListener(v -> {
|
||||
boolean willBeEnabled = !prefs.getWatchdogEnable();
|
||||
if (willBeEnabled) {
|
||||
BiometricHelper.prompt(MainActivity.this,
|
||||
getString(R.string.unlock_watchdog_title),
|
||||
getString(R.string.unlock_watchdog_subtitle),
|
||||
() -> setWatchdogState(true));
|
||||
} else {
|
||||
setWatchdogState(false);
|
||||
}
|
||||
});
|
||||
btnClearLog.setOnClickListener(this);
|
||||
btnCopyLog.setOnClickListener(this);
|
||||
themeToggle.setOnClickListener(v -> toggleTheme());
|
||||
|
|
@ -231,12 +292,38 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
checkbox_global.setOnClickListener(this);
|
||||
button_apps.setOnClickListener(this);
|
||||
button_save.setOnClickListener(this);
|
||||
button_control.setOnClickListener(this);
|
||||
|
||||
btnServerControl.setOnClickListener(v -> {
|
||||
// We're simplifying the action for now to test
|
||||
if (btnServerControl.getText().toString().contains("Launch")) {
|
||||
btnServerControl.setText("Starting...");
|
||||
btnServerControl.setAlpha(0.7f); // Efecto visual de "Cargando"
|
||||
startTermuxEnvironmentVisible("--start");
|
||||
} else {
|
||||
// TODO: We'll add the VPN biometric validation here later
|
||||
btnServerControl.setText("Stopping...");
|
||||
btnServerControl.setAlpha(0.7f);
|
||||
startTermuxEnvironmentVisible("--stop");
|
||||
|
||||
// Automatically turn off the Watchdog if we take down the server.
|
||||
if (prefs.getWatchdogEnable()) setWatchdogState(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Logic to open the WebView (PortalActivity)
|
||||
// button_browse_content.setOnClickListener(v -> {
|
||||
// Intent intent = new Intent(MainActivity.this, PortalActivity.class);
|
||||
// // We tell the Portal exactly where to go
|
||||
// String urlToLoad = prefs.getEnable() ? "http://box/home" : "http://localhost:8085/home";
|
||||
// intent.putExtra("TARGET_URL", urlToLoad);
|
||||
// startActivity(intent);
|
||||
// });
|
||||
button_browse_content.setOnClickListener(v -> {
|
||||
if (currentTargetUrl != null) {
|
||||
Intent intent = new Intent(MainActivity.this, PortalActivity.class);
|
||||
intent.putExtra("TARGET_URL", currentTargetUrl);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
connectionLog.setMovementMethod(new ScrollingMovementMethod());
|
||||
|
|
@ -267,7 +354,8 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
@Override
|
||||
public void run() {
|
||||
checkServerStatus();
|
||||
serverCheckHandler.postDelayed(this, currentCheckInterval);
|
||||
updateConnectivityStatus(); // Check Wi-Fi & Hotspot states
|
||||
serverCheckHandler.postDelayed(this, CHECK_INTERVAL_MS);
|
||||
}
|
||||
};
|
||||
serverCheckHandler.post(serverCheckRunnable);
|
||||
|
|
@ -276,7 +364,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
private void showBatterySnackbar() {
|
||||
View rootView = findViewById(android.R.id.content);
|
||||
Snackbar.make(rootView, R.string.battery_opt_denied, Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(R.string.fix_action, v -> checkBatteryOptimizations())
|
||||
.setAction(R.string.fix_action, v -> BatteryUtils.checkAndPromptOptimizations(MainActivity.this, batteryOptLauncher))
|
||||
.show();
|
||||
}
|
||||
|
||||
|
|
@ -298,20 +386,104 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
}
|
||||
}
|
||||
|
||||
private boolean pingUrl(String urlStr, boolean useProxy) {
|
||||
try {
|
||||
URL url = new URL(urlStr);
|
||||
HttpURLConnection conn;
|
||||
|
||||
if (useProxy) {
|
||||
// We routed the request directly to the app's SOCKS proxy
|
||||
int socksPort = prefs.getSocksPort(); // generally 1080
|
||||
Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", socksPort));
|
||||
conn = (HttpURLConnection) url.openConnection(proxy);
|
||||
} else {
|
||||
// Normal request (for localhost)
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
}
|
||||
|
||||
conn.setUseCaches(false);
|
||||
conn.setConnectTimeout(1500);
|
||||
conn.setReadTimeout(1500);
|
||||
conn.setRequestMethod("GET");
|
||||
return (conn.getResponseCode() >= 200 && conn.getResponseCode() < 400);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void runNegotiationSequence() {
|
||||
isNegotiating = true;
|
||||
runOnUiThread(() -> {
|
||||
startExplorePulse(); // The orange button starts to beat.
|
||||
updateUIColorsAndVisibility(); // We forced an immediate visual update
|
||||
});
|
||||
|
||||
new Thread(() -> {
|
||||
boolean boxAlive = false;
|
||||
|
||||
// Attempt 1 (0 seconds)
|
||||
boxAlive = pingUrl("http://box/home", true);
|
||||
|
||||
// Attempt 2 (At 2 seconds)
|
||||
if (!boxAlive) {
|
||||
try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
|
||||
boxAlive = pingUrl("http://box/home", true);
|
||||
}
|
||||
|
||||
// Attempt 3 (At 3 seconds)
|
||||
if (!boxAlive) {
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
|
||||
boxAlive = pingUrl("http://box/home", true);
|
||||
}
|
||||
|
||||
// We validate if localhost serves as a fallback.
|
||||
boolean localAlive = pingUrl("http://localhost:8085/home", false);
|
||||
|
||||
// We evaluate the results
|
||||
isNegotiating = false;
|
||||
isServerAlive = boxAlive || localAlive;
|
||||
isProxyDegraded = !boxAlive && localAlive; // Tunnel on, but proxy dead
|
||||
|
||||
if (boxAlive) {
|
||||
currentTargetUrl = "http://box/home";
|
||||
} else if (localAlive) {
|
||||
currentTargetUrl = "http://localhost:8085/home";
|
||||
} else {
|
||||
currentTargetUrl = null;
|
||||
}
|
||||
|
||||
runOnUiThread(this::updateUIColorsAndVisibility);
|
||||
}).start();
|
||||
}
|
||||
private void prepareVpn() {
|
||||
Intent intent = VpnService.prepare(MainActivity.this);
|
||||
if (intent != null) {
|
||||
vpnPermissionLauncher.launch(intent);
|
||||
} else {
|
||||
if (prefs.getEnable()) connectVpn();
|
||||
checkBatteryOptimizations();
|
||||
BatteryUtils.checkAndPromptOptimizations(MainActivity.this, batteryOptLauncher);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLogToggle() {
|
||||
boolean isOpening = connectionLog.getVisibility() == View.GONE;
|
||||
if (isOpening) {
|
||||
readBlackBoxLogs();
|
||||
if (isReadingLogs) return;
|
||||
isReadingLogs = true;
|
||||
if (logProgress != null) logProgress.setVisibility(View.VISIBLE);
|
||||
|
||||
// We delegate the reading to the Manager
|
||||
LogManager.readLogsAsync(this, (logContent, isRapidGrowth) -> {
|
||||
if (connectionLog != null) {
|
||||
connectionLog.setText(logContent);
|
||||
scrollToBottom();
|
||||
}
|
||||
if (logProgress != null) logProgress.setVisibility(View.GONE);
|
||||
if (logWarning != null) logWarning.setVisibility(isRapidGrowth ? View.VISIBLE : View.GONE);
|
||||
updateLogSizeUI();
|
||||
isReadingLogs = false;
|
||||
});
|
||||
|
||||
startLogSizeUpdates();
|
||||
} else {
|
||||
stopLogSizeUpdates();
|
||||
|
|
@ -332,16 +504,8 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
|
||||
private void updateLogSizeUI() {
|
||||
if (logSizeText == null) return;
|
||||
File logFile = new File(getFilesDir(), "watchdog_heartbeat_log.txt");
|
||||
long size = logFile.exists() ? logFile.length() : 0;
|
||||
String sizeStr;
|
||||
if (size < 1024) {
|
||||
sizeStr = size + " B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
sizeStr = String.format(Locale.getDefault(), "%.1f KB", size / 1024.0);
|
||||
} else {
|
||||
sizeStr = String.format(Locale.getDefault(), "%.2f MB", size / (1024.0 * 1024.0));
|
||||
}
|
||||
// The LogManager class does the calculation
|
||||
String sizeStr = LogManager.getFormattedSize(this);
|
||||
logSizeText.setText(getString(R.string.log_size_format, sizeStr));
|
||||
}
|
||||
|
||||
|
|
@ -351,51 +515,6 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
addToLog(getString(R.string.vpn_permission_granted));
|
||||
}
|
||||
|
||||
private void readBlackBoxLogs() {
|
||||
if (isReadingLogs) return;
|
||||
isReadingLogs = true;
|
||||
if (logProgress != null) logProgress.setVisibility(View.VISIBLE);
|
||||
|
||||
new Thread(() -> {
|
||||
File logFile = new File(getFilesDir(), "watchdog_heartbeat_log.txt");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (!logFile.exists()) {
|
||||
sb.append(getString(R.string.no_blackbox_found)).append("\n");
|
||||
} else {
|
||||
sb.append(getString(R.string.loading_history)).append("\n");
|
||||
try (BufferedReader br = new BufferedReader(new FileReader(logFile))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
sb.append(line).append("\n");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
sb.append(getString(R.string.error_reading_history, e.getMessage())).append("\n");
|
||||
}
|
||||
sb.append(getString(R.string.end_of_history)).append("\n");
|
||||
}
|
||||
|
||||
final String result = sb.toString();
|
||||
SharedPreferences internalPrefs = getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
|
||||
final boolean isRapid = internalPrefs.getBoolean(IIABWatchdog.PREF_RAPID_GROWTH, false);
|
||||
|
||||
runOnUiThread(() -> {
|
||||
if (connectionLog != null) {
|
||||
connectionLog.setText(result);
|
||||
scrollToBottom();
|
||||
}
|
||||
if (logProgress != null) {
|
||||
logProgress.setVisibility(View.GONE);
|
||||
}
|
||||
if (logWarning != null) {
|
||||
logWarning.setVisibility(isRapid ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
updateLogSizeUI();
|
||||
isReadingLogs = false;
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void setVersionFooter() {
|
||||
try {
|
||||
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||
|
|
@ -424,6 +543,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
showBatterySnackbar();
|
||||
}
|
||||
}
|
||||
updateConnectivityStatus(); // Force instant UI refresh when returning to app
|
||||
|
||||
if (getIntent() != null && getIntent().getBooleanExtra(VpnRecoveryReceiver.EXTRA_RECOVERY, false)) {
|
||||
addToLog(getString(R.string.recovery_pulse_received));
|
||||
|
|
@ -444,73 +564,6 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
setIntent(intent);
|
||||
}
|
||||
|
||||
private void checkBatteryOptimizations() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) {
|
||||
String manufacturer = Build.MANUFACTURER.toLowerCase();
|
||||
String message = getString(R.string.battery_opt_msg);
|
||||
|
||||
if (manufacturer.contains("oppo") || manufacturer.contains("realme") || manufacturer.contains("xiaomi")) {
|
||||
|
||||
if (manufacturer.contains("oppo") || manufacturer.contains("realme")) {
|
||||
message += getString(R.string.battery_opt_oppo_extra);
|
||||
} else if (manufacturer.contains("xiaomi")) {
|
||||
message += getString(R.string.battery_opt_xiaomi_extra);
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.battery_opt_title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.go_to_settings, (dialog, which) -> openBatterySettings(manufacturer))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
} else {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||
batteryOptLauncher.launch(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void openBatterySettings(String manufacturer) {
|
||||
boolean success = false;
|
||||
|
||||
if (manufacturer.contains("oppo") || manufacturer.contains("realme")) {
|
||||
try {
|
||||
Intent intent = new Intent();
|
||||
intent.setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity"));
|
||||
startActivity(intent);
|
||||
success = true;
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
Intent intent = new Intent();
|
||||
intent.setComponent(new ComponentName("com.coloros.oppoguardelf", "com.coloros.oppoguardelf.Permission.BackgroundAllowAppListActivity"));
|
||||
startActivity(intent);
|
||||
success = true;
|
||||
} catch (Exception e2) {}
|
||||
}
|
||||
} else if (manufacturer.contains("xiaomi")) {
|
||||
try {
|
||||
Intent intent = new Intent("miui.intent.action.APP_BATTERY_SAVER_SETTINGS");
|
||||
intent.setComponent(new ComponentName("com.miui.powerkeeper", "com.miui.powerkeeper.ui.HiddenAppsConfigActivity"));
|
||||
intent.putExtra("package_name", getPackageName());
|
||||
intent.putExtra("package_label", getString(R.string.app_name));
|
||||
startActivity(intent);
|
||||
success = true;
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||
startActivity(intent);
|
||||
} catch (Exception ex) {}
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleTheme() {
|
||||
SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
|
||||
int currentMode = AppCompatDelegate.getDefaultNightMode();
|
||||
|
|
@ -543,7 +596,11 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
IntentFilter filter = new IntentFilter(IIABWatchdog.ACTION_LOG_MESSAGE);
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(IIABWatchdog.ACTION_LOG_MESSAGE);
|
||||
filter.addAction(WatchdogService.ACTION_STATE_STARTED);
|
||||
filter.addAction(WatchdogService.ACTION_STATE_STOPPED);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(logReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
|
||||
} else {
|
||||
|
|
@ -591,145 +648,106 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
}
|
||||
|
||||
private void resetLogFile() {
|
||||
File logFile = new File(getFilesDir(), "watchdog_heartbeat_log.txt");
|
||||
try (PrintWriter pw = new PrintWriter(logFile)) {
|
||||
pw.print("");
|
||||
LogManager.clearLogs(this, new LogManager.LogClearCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
connectionLog.setText("");
|
||||
addToLog(getString(R.string.log_reset_user));
|
||||
getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE).edit().putBoolean(IIABWatchdog.PREF_RAPID_GROWTH, false).apply();
|
||||
if (logWarning != null) logWarning.setVisibility(View.GONE);
|
||||
updateLogSizeUI();
|
||||
Toast.makeText(this, R.string.log_cleared_toast, Toast.LENGTH_SHORT).show();
|
||||
} catch (IOException e) {
|
||||
Toast.makeText(this, getString(R.string.failed_reset_log, e.getMessage()), Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(MainActivity.this, R.string.log_cleared_toast, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String message) {
|
||||
Toast.makeText(MainActivity.this, getString(R.string.failed_reset_log, message), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleWatchdogClick() {
|
||||
toggleWatchdog(prefs.getWatchdogEnable());
|
||||
setWatchdogState(!prefs.getWatchdogEnable());
|
||||
}
|
||||
|
||||
private void toggleWatchdog(boolean stop) {
|
||||
prefs.setWatchdogEnable(!stop);
|
||||
private void setWatchdogState(boolean enable) {
|
||||
prefs.setWatchdogEnable(enable);
|
||||
Intent intent = new Intent(this, WatchdogService.class);
|
||||
if (stop) {
|
||||
stopService(intent);
|
||||
addToLog(getString(R.string.watchdog_stopped));
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForegroundService(intent.setAction(WatchdogService.ACTION_START));
|
||||
else startService(intent.setAction(WatchdogService.ACTION_START));
|
||||
|
||||
if (enable) {
|
||||
intent.setAction(WatchdogService.ACTION_START);
|
||||
addToLog(getString(R.string.watchdog_started));
|
||||
if (isServerAlive) startFusionPulse();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(intent);
|
||||
} else {
|
||||
startService(intent);
|
||||
}
|
||||
} else {
|
||||
addToLog(getString(R.string.watchdog_stopped));
|
||||
startExitPulse();
|
||||
stopService(intent);
|
||||
}
|
||||
|
||||
updateUI();
|
||||
updateUIColorsAndVisibility(isServerAlive);
|
||||
}
|
||||
|
||||
private void handleControlClick() {
|
||||
if (prefs.getEnable()) showBiometricPrompt();
|
||||
else {
|
||||
BiometricManager bm = BiometricManager.from(this);
|
||||
int auth = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) auth = BiometricManager.Authenticators.BIOMETRIC_WEAK | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
android.app.KeyguardManager km = (android.app.KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
|
||||
if (bm.canAuthenticate(auth) == BiometricManager.BIOMETRIC_SUCCESS || (km != null && km.isDeviceSecure())) {
|
||||
addToLog(getString(R.string.user_initiated_conn));
|
||||
toggleService(false);
|
||||
} else showEnrollmentDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void showEnrollmentDialog() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.security_required_title)
|
||||
.setMessage(R.string.security_required_msg)
|
||||
.setPositiveButton(R.string.go_to_settings, (dialog, which) -> {
|
||||
Intent intent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
|
||||
startActivity(intent);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showBiometricPrompt() {
|
||||
Executor ex = ContextCompat.getMainExecutor(this);
|
||||
BiometricPrompt bp = new BiometricPrompt(this, ex, new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
if (prefs.getEnable()) {
|
||||
BiometricHelper.prompt(this,
|
||||
getString(R.string.auth_required_title),
|
||||
getString(R.string.auth_required_subtitle),
|
||||
() -> {
|
||||
addToLog(getString(R.string.auth_success_disconnect));
|
||||
toggleService(true);
|
||||
}
|
||||
});
|
||||
int auth = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
bp.authenticate(new BiometricPrompt.PromptInfo.Builder().setTitle(getString(R.string.auth_required_title)).setSubtitle(getString(R.string.auth_required_subtitle)).setAllowedAuthenticators(auth).build());
|
||||
} else {
|
||||
if (BiometricHelper.isDeviceSecure(this)) {
|
||||
addToLog(getString(R.string.user_initiated_conn));
|
||||
toggleService(false);
|
||||
} else {
|
||||
BiometricHelper.showEnrollmentDialog(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Secure Advanced Settings Menu ---
|
||||
private void handleConfigToggle() {
|
||||
if (configLayout.getVisibility() == View.GONE) {
|
||||
BiometricManager bm = BiometricManager.from(this);
|
||||
int auth = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
auth = BiometricManager.Authenticators.BIOMETRIC_WEAK | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
}
|
||||
android.app.KeyguardManager km = (android.app.KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
|
||||
|
||||
if (bm.canAuthenticate(auth) == BiometricManager.BIOMETRIC_SUCCESS || (km != null && km.isDeviceSecure())) {
|
||||
showConfigBiometricPrompt();
|
||||
if (BiometricHelper.isDeviceSecure(this)) {
|
||||
BiometricHelper.prompt(this,
|
||||
getString(R.string.auth_required_title),
|
||||
getString(R.string.auth_required_subtitle),
|
||||
() -> toggleVisibility(configLayout, configLabel, getString(R.string.advanced_settings_label)));
|
||||
} else {
|
||||
showEnrollmentDialog();
|
||||
BiometricHelper.showEnrollmentDialog(this);
|
||||
}
|
||||
} else {
|
||||
toggleVisibility(configLayout, configLabel, getString(R.string.advanced_settings_label));
|
||||
}
|
||||
}
|
||||
|
||||
private void showConfigBiometricPrompt() {
|
||||
Executor ex = ContextCompat.getMainExecutor(this);
|
||||
BiometricPrompt bp = new BiometricPrompt(this, ex, new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
toggleVisibility(configLayout, configLabel, getString(R.string.advanced_settings_label));
|
||||
}
|
||||
});
|
||||
|
||||
int auth = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
auth = BiometricManager.Authenticators.BIOMETRIC_WEAK | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
}
|
||||
|
||||
bp.authenticate(new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(getString(R.string.auth_required_title))
|
||||
.setSubtitle(getString(R.string.auth_required_subtitle))
|
||||
.setAllowedAuthenticators(auth)
|
||||
.build());
|
||||
}
|
||||
|
||||
private void showWatchdogBiometricPrompt() {
|
||||
Executor ex = ContextCompat.getMainExecutor(this);
|
||||
BiometricPrompt bp = new BiometricPrompt(this, new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
toggleWatchdog(true);
|
||||
}
|
||||
});
|
||||
int auth = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
bp.authenticate(new BiometricPrompt.PromptInfo.Builder().setTitle(getString(R.string.unlock_watchdog_title)).setSubtitle(getString(R.string.unlock_watchdog_subtitle)).setAllowedAuthenticators(auth).build());
|
||||
}
|
||||
|
||||
private void toggleService(boolean stop) {
|
||||
prefs.setEnable(!stop);
|
||||
savePrefs();
|
||||
updateUI();
|
||||
Intent intent = new Intent(this, TProxyService.class);
|
||||
startService(intent.setAction(stop ? TProxyService.ACTION_DISCONNECT : TProxyService.ACTION_CONNECT));
|
||||
addToLog(getString(stop ? R.string.vpn_stopping : R.string.vpn_starting));
|
||||
|
||||
if (!stop) {
|
||||
runNegotiationSequence();
|
||||
} else {
|
||||
updateUIColorsAndVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
boolean vpnActive = prefs.getEnable();
|
||||
boolean watchdogActive = prefs.getWatchdogEnable();
|
||||
|
||||
if (dashboardManager != null) dashboardManager.setTunnelState(vpnActive, isProxyDegraded);
|
||||
|
||||
if (vpnActive) {
|
||||
button_control.setText(R.string.control_disable);
|
||||
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_on));
|
||||
|
|
@ -739,10 +757,8 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
}
|
||||
if (watchdogActive) {
|
||||
watchdogControl.setText(R.string.watchdog_disable);
|
||||
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on));
|
||||
} else {
|
||||
watchdogControl.setText(R.string.watchdog_enable);
|
||||
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_off));
|
||||
}
|
||||
edittext_socks_addr.setText(prefs.getSocksAddress());
|
||||
edittext_socks_udp_addr.setText(prefs.getSocksUdpAddress());
|
||||
|
|
@ -769,52 +785,274 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
}
|
||||
|
||||
private void checkServerStatus() {
|
||||
new Thread(() -> {
|
||||
boolean isReachable = false;
|
||||
try {
|
||||
URL url = new URL("http://localhost:8085/home");
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(1500);
|
||||
connection.setReadTimeout(1500);
|
||||
connection.setRequestMethod("GET");
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (isNegotiating) return;
|
||||
|
||||
isReachable = (responseCode >= 200 && responseCode < 400);
|
||||
} catch (Exception e) {
|
||||
isReachable = false;
|
||||
new Thread(() -> {
|
||||
boolean localAlive = pingUrl("http://localhost:8085/home", false);
|
||||
boolean vpnOn = prefs.getEnable();
|
||||
boolean boxAlive = false;
|
||||
|
||||
if (vpnOn) {
|
||||
// The passive radar must also use the proxy to test the tunnel.
|
||||
boxAlive = pingUrl("http://box/home", true);
|
||||
isProxyDegraded = !boxAlive && localAlive;
|
||||
} else {
|
||||
isProxyDegraded = false;
|
||||
}
|
||||
|
||||
final boolean serverAlive = isReachable;
|
||||
runOnUiThread(() -> updateUIColorsAndVisibility(serverAlive));
|
||||
isServerAlive = localAlive || boxAlive;
|
||||
|
||||
if (vpnOn && boxAlive) {
|
||||
currentTargetUrl = "http://box/home";
|
||||
} else if (localAlive) {
|
||||
currentTargetUrl = "http://localhost:8085/home";
|
||||
} else {
|
||||
currentTargetUrl = null;
|
||||
}
|
||||
|
||||
runOnUiThread(this::updateUIColorsAndVisibility);
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void updateUIColorsAndVisibility() {
|
||||
boolean isVpnActive = prefs.getEnable();
|
||||
boolean isWatchdogOn = prefs.getWatchdogEnable();
|
||||
|
||||
// Draw island
|
||||
if (dashboardManager != null) {
|
||||
dashboardManager.setTunnelState(isVpnActive, isProxyDegraded);
|
||||
}
|
||||
|
||||
// Draw main button
|
||||
if (isVpnActive) {
|
||||
button_control.setText(R.string.control_disable);
|
||||
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, isServerAlive ? R.color.btn_vpn_on : R.color.btn_vpn_on_dim));
|
||||
} else {
|
||||
button_control.setText(R.string.control_enable);
|
||||
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, isServerAlive ? R.color.btn_vpn_off : R.color.btn_vpn_off_dim));
|
||||
}
|
||||
|
||||
// 3. Draw Explore Content button
|
||||
if (!isServerAlive) {
|
||||
// State 1: Stopped
|
||||
stopExplorePulse();
|
||||
button_browse_content.setEnabled(false);
|
||||
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_explore_disabled));
|
||||
button_browse_content.setAlpha(1.0f);
|
||||
button_browse_content.setTextColor(Color.parseColor("#888888")); // Texto grisáceo apagado
|
||||
} else if (isNegotiating) {
|
||||
// State 2: Negotiating
|
||||
button_browse_content.setEnabled(true);
|
||||
button_browse_content.setTextColor(Color.WHITE);
|
||||
// (El latido ya maneja la opacidad al 100%)
|
||||
} else {
|
||||
stopExplorePulse();
|
||||
button_browse_content.setEnabled(true);
|
||||
button_browse_content.setTextColor(Color.WHITE);
|
||||
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_explore_ready));
|
||||
|
||||
if (isVpnActive && !isProxyDegraded) {
|
||||
// State 5: All good
|
||||
button_browse_content.setAlpha(1.0f);
|
||||
} else {
|
||||
// State 2: local or state 4
|
||||
button_browse_content.setAlpha(0.6f);
|
||||
}
|
||||
}
|
||||
|
||||
// FUSION LOGIC - Watchdog
|
||||
btnServerControl.setAlpha(1.0f);
|
||||
if (isServerAlive) {
|
||||
btnServerControl.setText("🛑 Stop Server");
|
||||
if (isWatchdogOn) {
|
||||
deckContainer.setBackgroundColor(Color.parseColor("#44FF9800"));
|
||||
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on));
|
||||
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on));
|
||||
} else {
|
||||
if (fusionAnimator == null || !fusionAnimator.isRunning()) deckContainer.setBackgroundColor(Color.TRANSPARENT);
|
||||
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_danger));
|
||||
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_off));
|
||||
}
|
||||
} else {
|
||||
deckContainer.setBackgroundColor(Color.TRANSPARENT);
|
||||
btnServerControl.setText("🚀 Launch Server");
|
||||
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_success));
|
||||
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, isWatchdogOn ? R.color.btn_watchdog_on : R.color.btn_watchdog_off));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUIColorsAndVisibility(boolean isServerAlive) {
|
||||
boolean isVpnActive = prefs.getEnable();
|
||||
|
||||
if (!isServerAlive) {
|
||||
currentCheckInterval = MIN_CHECK_INTERVAL;
|
||||
|
||||
button_control.setEnabled(false); // Disable ESPW click
|
||||
button_browse_content.setVisibility(View.GONE);
|
||||
stopExplorePulse(); // We stop the animation if the server dies
|
||||
|
||||
if (isVpnActive) {
|
||||
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_on_dim));
|
||||
} else {
|
||||
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_off_dim));
|
||||
}
|
||||
} else {
|
||||
currentCheckInterval = Math.min((int)(currentCheckInterval * 1.5), MAX_CHECK_INTERVAL);
|
||||
|
||||
button_control.setEnabled(true); // Enable ESPW click
|
||||
button_browse_content.setVisibility(View.VISIBLE); // Always visible if server is alive
|
||||
button_browse_content.setVisibility(View.VISIBLE);
|
||||
|
||||
updateUI();
|
||||
// Heart rate and diluted color control
|
||||
if (isVpnActive) {
|
||||
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_explore_ready));
|
||||
startExplorePulse();
|
||||
} else {
|
||||
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_explore_disabled));
|
||||
stopExplorePulse();
|
||||
}
|
||||
}
|
||||
// --- THE FUSION LOGIC (CASSETTE DECK) ---
|
||||
boolean isWatchdogOn = prefs.getWatchdogEnable();
|
||||
btnServerControl.setAlpha(1.0f); // Reset opacity
|
||||
|
||||
if (isServerAlive) {
|
||||
btnServerControl.setText("🛑 Stop Server");
|
||||
|
||||
if (isWatchdogOn) {
|
||||
// FUSION: Server alive + Watchdog active
|
||||
// DELETED the stopFusionPulse() from here so the animation can live!
|
||||
deckContainer.setBackgroundColor(Color.parseColor("#44FF9800")); // Bright border/background
|
||||
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on));
|
||||
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on));
|
||||
} else {
|
||||
// Sever alive, without Watchdog
|
||||
if (fusionAnimator == null || !fusionAnimator.isRunning()) {
|
||||
deckContainer.setBackgroundColor(Color.TRANSPARENT);
|
||||
}
|
||||
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_danger)); // Red
|
||||
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_off));
|
||||
}
|
||||
} else {
|
||||
// Server offline
|
||||
deckContainer.setBackgroundColor(Color.TRANSPARENT);
|
||||
btnServerControl.setText("🚀 Launch Server");
|
||||
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_success)); // Verde
|
||||
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, isWatchdogOn ? R.color.btn_watchdog_on : R.color.btn_watchdog_off));
|
||||
}
|
||||
}
|
||||
|
||||
private void startFusionPulse() {
|
||||
deckContainer.setBackgroundColor(Color.parseColor("#44FF9800"));
|
||||
if (fusionAnimator != null && fusionAnimator.isRunning()) fusionAnimator.cancel();
|
||||
|
||||
pulseStartTime = System.currentTimeMillis();
|
||||
|
||||
// Pulses infinitely until the Service broadcast stops it
|
||||
fusionAnimator = ObjectAnimator.ofFloat(deckContainer, "alpha", 1f, 0.4f);
|
||||
fusionAnimator.setDuration(600);
|
||||
fusionAnimator.setRepeatCount(ObjectAnimator.INFINITE);
|
||||
fusionAnimator.setRepeatMode(ObjectAnimator.REVERSE);
|
||||
fusionAnimator.start();
|
||||
}
|
||||
|
||||
private void startExitPulse() {
|
||||
if (fusionAnimator != null && fusionAnimator.isRunning()) fusionAnimator.cancel();
|
||||
|
||||
// A slower, deliberate pulse infinitely until the Service + 1.5s delay stops it
|
||||
fusionAnimator = ObjectAnimator.ofFloat(deckContainer, "alpha", deckContainer.getAlpha(), 0.3f);
|
||||
fusionAnimator.setDuration(800);
|
||||
fusionAnimator.setRepeatCount(ObjectAnimator.INFINITE);
|
||||
fusionAnimator.setRepeatMode(ObjectAnimator.REVERSE);
|
||||
fusionAnimator.start();
|
||||
}
|
||||
|
||||
private void startExplorePulse() {
|
||||
button_browse_content.setAlpha(1.0f); // 100% Bright Orange
|
||||
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_explore_ready));
|
||||
|
||||
if (exploreAnimator == null) {
|
||||
android.animation.PropertyValuesHolder scaleX = android.animation.PropertyValuesHolder.ofFloat(View.SCALE_X, 1.0f, 1.03f);
|
||||
android.animation.PropertyValuesHolder scaleY = android.animation.PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.0f, 1.03f);
|
||||
exploreAnimator = android.animation.ObjectAnimator.ofPropertyValuesHolder(button_browse_content, scaleX, scaleY);
|
||||
exploreAnimator.setDuration(800); // 800ms per heartbeat
|
||||
exploreAnimator.setRepeatCount(android.animation.ObjectAnimator.INFINITE);
|
||||
exploreAnimator.setRepeatMode(android.animation.ObjectAnimator.REVERSE);
|
||||
}
|
||||
if (!exploreAnimator.isRunning()) exploreAnimator.start();
|
||||
}
|
||||
|
||||
private void stopExplorePulse() {
|
||||
if (exploreAnimator != null && exploreAnimator.isRunning()) {
|
||||
exploreAnimator.cancel();
|
||||
}
|
||||
// Restore to normal size
|
||||
button_browse_content.setScaleX(1.0f);
|
||||
button_browse_content.setScaleY(1.0f);
|
||||
|
||||
// Diluted orange (ready, but waiting for the tunnel)
|
||||
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_explore_ready));
|
||||
button_browse_content.setAlpha(0.6f);
|
||||
}
|
||||
|
||||
private void finalizeEntryPulse() {
|
||||
if (fusionAnimator != null) fusionAnimator.cancel();
|
||||
deckContainer.setAlpha(1f); // Lock solid instantly
|
||||
}
|
||||
|
||||
private void finalizeExitPulse() {
|
||||
if (fusionAnimator != null) fusionAnimator.cancel();
|
||||
deckContainer.animate()
|
||||
.alpha(1f)
|
||||
.setDuration(300)
|
||||
.withEndAction(() -> {
|
||||
deckContainer.setBackgroundColor(Color.TRANSPARENT);
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
private void startTermuxEnvironmentVisible(String actionFlag) {
|
||||
Intent intent = new Intent();
|
||||
intent.setClassName("com.termux", "com.termux.app.RunCommandService");
|
||||
intent.setAction("com.termux.RUN_COMMAND");
|
||||
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/iiab-termux");
|
||||
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{actionFlag});
|
||||
intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home");
|
||||
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", false);
|
||||
intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0");
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(intent);
|
||||
} else {
|
||||
startService(intent);
|
||||
}
|
||||
addToLog("Sent to Termux: " + actionFlag);
|
||||
} catch (Exception e) {
|
||||
addToLog("CRITICAL: Failed Termux Intent: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateConnectivityStatus() {
|
||||
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
boolean isWifiOn = wifiManager != null && wifiManager.isWifiEnabled();
|
||||
boolean isHotspotOn = false;
|
||||
|
||||
try {
|
||||
// 1. Try standard reflection (Works on older Androids)
|
||||
java.lang.reflect.Method method = wifiManager.getClass().getDeclaredMethod("isWifiApEnabled");
|
||||
method.setAccessible(true);
|
||||
isHotspotOn = (Boolean) method.invoke(wifiManager);
|
||||
} catch (Throwable e) {
|
||||
// 2. Fallback for Android 10+: Check physical network interfaces
|
||||
try {
|
||||
java.util.Enumeration<java.net.NetworkInterface> interfaces = java.net.NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces != null && interfaces.hasMoreElements()) {
|
||||
java.net.NetworkInterface iface = interfaces.nextElement();
|
||||
String name = iface.getName();
|
||||
if ((name.startsWith("ap") || name.startsWith("swlan")) && iface.isUp()) {
|
||||
isHotspotOn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {}
|
||||
}
|
||||
|
||||
// Let the Dashboard handle the LEDs!
|
||||
if (dashboardManager != null) dashboardManager.updateConnectivityLeds(isWifiOn, isHotspotOn);
|
||||
}
|
||||
|
||||
private void savePrefs() {
|
||||
|
|
|
|||
|
|
@ -87,10 +87,19 @@ public class PortalActivity extends AppCompatActivity {
|
|||
|
||||
Preferences prefs = new Preferences(this);
|
||||
boolean isVpnActive = prefs.getEnable();
|
||||
String targetUrl = isVpnActive ? "http://box/" : "http://localhost:8085/home";
|
||||
|
||||
String rawUrl = getIntent().getStringExtra("TARGET_URL");
|
||||
|
||||
// If for some strange reason the URL arrives empty, we use the security fallback
|
||||
if (rawUrl == null || rawUrl.isEmpty()) {
|
||||
rawUrl = "http://localhost:8085/home";
|
||||
}
|
||||
|
||||
// 1. Damos alcance global seguro a la URL para todos los lambdas de aquí en adelante
|
||||
final String finalTargetUrl = rawUrl;
|
||||
|
||||
btnHome.setOnClickListener(v -> {
|
||||
webView.loadUrl(targetUrl);
|
||||
webView.loadUrl(finalTargetUrl); // Usamos la variable final
|
||||
resetTimer.run();
|
||||
});
|
||||
|
||||
|
|
@ -146,6 +155,22 @@ public class PortalActivity extends AppCompatActivity {
|
|||
// Restore cache for normal browsing speed
|
||||
view.getSettings().setCacheMode(android.webkit.WebSettings.LOAD_DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, android.webkit.WebResourceRequest request, android.webkit.WebResourceError error) {
|
||||
super.onReceivedError(view, request, error);
|
||||
|
||||
if (request.isForMainFrame()) {
|
||||
String customErrorHtml = "<html><body style='background-color:#1A1A1A;color:#FFFFFF;text-align:center;padding-top:50px;font-family:sans-serif;'>"
|
||||
+ "<h2>⚠️ Connection Failed</h2>"
|
||||
+ "<p>Unable to reach the secure environment.</p>"
|
||||
+ "<p style='color:#888;font-size:12px;'>Error: " + error.getDescription() + "</p>"
|
||||
+ "</body></html>";
|
||||
view.loadData(customErrorHtml, "text/html", "UTF-8");
|
||||
isPageLoading = false;
|
||||
btnReload.setText("↻");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// --- MANUALLY CLOSE BAR LOGIC ---
|
||||
|
|
@ -163,10 +188,10 @@ public class PortalActivity extends AppCompatActivity {
|
|||
int tempPort = prefs.getSocksPort();
|
||||
if (tempPort <= 0) tempPort = 1080;
|
||||
|
||||
// Variable safe to read in lambda
|
||||
// 3. Restauramos la variable segura para el puerto
|
||||
final int finalProxyPort = tempPort;
|
||||
|
||||
// 3. Proxy block (ONLY IF VPN IS ACTIVE)
|
||||
// 4. Proxy block (ONLY IF VPN IS ACTIVE)
|
||||
if (isVpnActive) {
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
|
||||
ProxyConfig proxyConfig = new ProxyConfig.Builder()
|
||||
|
|
@ -178,16 +203,16 @@ public class PortalActivity extends AppCompatActivity {
|
|||
ProxyController.getInstance().setProxyOverride(proxyConfig, executor, () -> {
|
||||
Log.d(TAG, "Proxy configured on port: " + finalProxyPort);
|
||||
// Load HTML only when proxy is ready
|
||||
webView.loadUrl(targetUrl);
|
||||
webView.loadUrl(finalTargetUrl);
|
||||
});
|
||||
} else {
|
||||
// Fallback for older devices
|
||||
Log.w(TAG, "Proxy Override not supported");
|
||||
webView.loadUrl(targetUrl);
|
||||
webView.loadUrl(finalTargetUrl);
|
||||
}
|
||||
} else {
|
||||
// VPN is OFF. Do NOT use proxy. Just load localhost directly.
|
||||
webView.loadUrl(targetUrl);
|
||||
webView.loadUrl(finalTargetUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ public class WatchdogService extends Service {
|
|||
public static final String ACTION_START = "org.iiab.controller.WATCHDOG_START";
|
||||
public static final String ACTION_STOP = "org.iiab.controller.WATCHDOG_STOP";
|
||||
public static final String ACTION_HEARTBEAT = "org.iiab.controller.HEARTBEAT";
|
||||
|
||||
public static final String ACTION_STATE_STARTED = "org.iiab.controller.WATCHDOG_STARTED";
|
||||
public static final String ACTION_STATE_STOPPED = "org.iiab.controller.WATCHDOG_STOPPED";
|
||||
private static final int HEARTBEAT_INTERVAL_MS = 20 * 1000;
|
||||
|
||||
@Override
|
||||
|
|
@ -36,12 +37,8 @@ public class WatchdogService extends Service {
|
|||
String action = intent.getAction();
|
||||
if (ACTION_START.equals(action)) {
|
||||
startWatchdog();
|
||||
} else if (ACTION_STOP.equals(action)) {
|
||||
stopWatchdog();
|
||||
return START_NOT_STICKY;
|
||||
} else if (ACTION_HEARTBEAT.equals(action)) {
|
||||
IIABWatchdog.performHeartbeat(this);
|
||||
// CRITICAL: Reschedule for the next pulse to create an infinite loop
|
||||
scheduleHeartbeat();
|
||||
}
|
||||
}
|
||||
|
|
@ -58,15 +55,26 @@ public class WatchdogService extends Service {
|
|||
|
||||
IIABWatchdog.logSessionStart(this);
|
||||
scheduleHeartbeat();
|
||||
|
||||
Intent startIntent = new Intent(ACTION_STATE_STARTED);
|
||||
startIntent.setPackage(getPackageName());
|
||||
sendBroadcast(startIntent);
|
||||
}
|
||||
|
||||
private void stopWatchdog() {
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
// 1. Avisamos inmediatamente a la UI que nos estamos apagando
|
||||
Intent stopIntent = new Intent(ACTION_STATE_STOPPED);
|
||||
stopIntent.setPackage(getPackageName());
|
||||
sendBroadcast(stopIntent);
|
||||
|
||||
// 2. Limpiamos la basura
|
||||
cancelHeartbeat();
|
||||
IIABWatchdog.logSessionStop(this);
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
private PendingIntent getHeartbeatPendingIntent() {
|
||||
Intent intent = new Intent(this, WatchdogService.class);
|
||||
intent.setAction(ACTION_HEARTBEAT);
|
||||
|
|
@ -101,12 +109,6 @@ public class WatchdogService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
stopWatchdog();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="#333333" /> <stroke android:width="1dp" android:color="#222222" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="#00FF00" /> <stroke android:width="2dp" android:color="#4400FF00" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="#FF9800" />
|
||||
<stroke android:width="2dp" android:color="#44FF9800" />
|
||||
</shape>
|
||||
|
|
@ -68,8 +68,88 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- HR below Watchdog -->
|
||||
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginBottom="20dp"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/dashboard_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:backgroundTint="#1A1A1A"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/dash_wifi"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:padding="12dp">
|
||||
<View
|
||||
android:layout_width="10dp"
|
||||
android:id="@+id/led_wifi"
|
||||
android:layout_height="10dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@drawable/led_off" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Wi-Fi"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/dash_hotspot"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:padding="12dp">
|
||||
<View
|
||||
android:id="@+id/led_hotspot"
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@drawable/led_off" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hotspot"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/dash_tunnel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:padding="12dp"
|
||||
android:visibility="gone">
|
||||
<View
|
||||
android:id="@+id/led_tunnel"
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@drawable/led_on_green" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tunnel"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- VPN Control Section -->
|
||||
<Button
|
||||
|
|
@ -85,6 +165,7 @@
|
|||
android:textAllCaps="false"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/control_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/vpn_description"
|
||||
|
|
@ -109,7 +190,7 @@
|
|||
android:backgroundTint="@color/btn_explore_disabled"
|
||||
android:textAllCaps="false"
|
||||
android:elevation="4dp"
|
||||
android:visibility="gone" />
|
||||
android:enabled="false" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/config_label"
|
||||
|
|
@ -250,19 +331,45 @@
|
|||
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Watchdog Control Section -->
|
||||
<LinearLayout
|
||||
android:id="@+id/deck_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="12dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="#00000000"
|
||||
android:padding="3dp"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false"
|
||||
android:weightSum="2">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_server_control"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="Launch Server"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#FFFFFF"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:backgroundTint="@color/btn_success"
|
||||
android:textAllCaps="false"/>
|
||||
<Button
|
||||
android:id="@+id/watchdog_control"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="90dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="@string/watchdog_enable"
|
||||
android:textSize="20sp"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#FFFFFF"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:backgroundTint="@color/btn_watchdog_off"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textAllCaps="false"/>
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@
|
|||
<string name="control_enable">Activar Safe Pocket Web</string>
|
||||
<string name="control_disable">Desactivar Safe Pocket Web</string>
|
||||
<string name="vpn_description">Habilite URLs amigables. Bloquee las amenazas.</string>
|
||||
<string name="watchdog_enable">Activar Watchdog Maestro</string>
|
||||
<string name="watchdog_disable">Desactivar Watchdog Maestro</string>
|
||||
<string name="watchdog_enable">Activar\nWatchdog Maestro</string>
|
||||
<string name="watchdog_disable">Desactivar\nWatchdog Maestro</string>
|
||||
<string name="log_reset_confirm_title">¿Reiniciar historial de log?</string>
|
||||
<string name="log_reset_confirm_msg">Esto borrará permanentemente todos los logs de conexión guardados. Esta acción no se puede deshacer.</string>
|
||||
<string name="log_warning_rapid_growth">El archivo de log está creciendo demasiado rápido, verifique si algo está fallando</string>
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@
|
|||
<string name="control_enable">Enable Safe Pocket Web</string>
|
||||
<string name="control_disable">Disable Safe Pocket Web</string>
|
||||
<string name="vpn_description">Enable friendly URLs. Lock out the threats.</string>
|
||||
<string name="watchdog_enable">Enable Master Watchdog</string>
|
||||
<string name="watchdog_disable">Disable Master Watchdog</string>
|
||||
<string name="watchdog_enable">Enable\nMaster Watchdog</string>
|
||||
<string name="watchdog_disable">Disable\nMaster Watchdog</string>
|
||||
<string name="log_reset_confirm_title">Reset Log History?</string>
|
||||
<string name="log_reset_confirm_msg">This will permanently delete all stored connection logs. This action cannot be undone.</string>
|
||||
<string name="log_warning_rapid_growth">The logging file is growing too rapidly, you might want to check if something is failing</string>
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@
|
|||
<string name="control_enable">Enable Safe Pocket Web</string>
|
||||
<string name="control_disable">Disable Safe Pocket Web</string>
|
||||
<string name="vpn_description">Enable friendly URLs. Lock out the threats.</string>
|
||||
<string name="watchdog_enable">Enable Master Watchdog</string>
|
||||
<string name="watchdog_disable">Disable Master Watchdog</string>
|
||||
<string name="watchdog_enable">Enable\nMaster Watchdog</string>
|
||||
<string name="watchdog_disable">Disable\nMaster Watchdog</string>
|
||||
<string name="log_reset_confirm_title">Reset Log History?</string>
|
||||
<string name="log_reset_confirm_msg">This will permanently delete all stored connection logs. This action cannot be undone.</string>
|
||||
<string name="log_warning_rapid_growth">The logging file is growing too rapidly, you might want to check if something is failing</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue