restructure pemissions request

This commit is contained in:
Luis Guzmán 2026-03-07 00:13:55 -06:00
parent 233e167eee
commit 3c1b920d42
6 changed files with 170 additions and 120 deletions

View File

@ -832,6 +832,18 @@
<option name="screenX" value="1080" /> <option name="screenX" value="1080" />
<option name="screenY" value="2340" /> <option name="screenY" value="2340" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="motorola" />
<option name="codename" value="gnevan" />
<option name="id" value="gnevan" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g stylus (2023)" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="34" /> <option name="api" value="34" />
<option name="brand" value="samsung" /> <option name="brand" value="samsung" />
@ -1218,6 +1230,18 @@
<option name="screenX" value="1856" /> <option name="screenX" value="1856" />
<option name="screenY" value="2160" /> <option name="screenY" value="2160" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
<option name="codename" value="r0qcsx" />
<option name="id" value="r0qcsx" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="S22" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="30" /> <option name="api" value="30" />
<option name="brand" value="google" /> <option name="brand" value="google" />

View File

@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-03-07T04:20:37.707409563Z"> <DropdownSelection timestamp="2026-03-07T04:28:01.557850134Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=69K7MB899PKJGQBI" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=a026a310" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

View File

@ -9,8 +9,8 @@ android {
applicationId "org.iiab.controller" applicationId "org.iiab.controller"
minSdkVersion 24 minSdkVersion 24
targetSdkVersion 34 targetSdkVersion 34
versionCode 22 versionCode 23
versionName "v0.1.26alpha" versionName "v0.1.27alpha"
setProperty("archivesBaseName", "$applicationId-$versionName") setProperty("archivesBaseName", "$applicationId-$versionName")
ndk { ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"

View File

@ -11,9 +11,9 @@
"type": "SINGLE", "type": "SINGLE",
"filters": [], "filters": [],
"attributes": [], "attributes": [],
"versionCode": 22, "versionCode": 23,
"versionName": "v0.1.26alpha", "versionName": "v0.1.27alpha",
"outputFile": "org.iiab.controller-v0.1.26alpha-release.apk" "outputFile": "org.iiab.controller-v0.1.27alpha-release.apk"
} }
], ],
"elementType": "File", "elementType": "File",

View File

@ -22,6 +22,7 @@ import android.content.IntentFilter;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -47,7 +48,6 @@ import android.provider.Settings;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.biometric.BiometricManager; import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt; import androidx.biometric.BiometricPrompt;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -55,10 +55,13 @@ import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.concurrent.Executor; import java.util.ArrayList;
import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.text.SimpleDateFormat;
import java.util.concurrent.Executor;
public class MainActivity extends AppCompatActivity implements View.OnClickListener { public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "IIAB-MainActivity"; private static final String TAG = "IIAB-MainActivity";
@ -95,11 +98,10 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
private ProgressBar logProgress; private ProgressBar logProgress;
private ActivityResultLauncher<Intent> vpnPermissionLauncher; private ActivityResultLauncher<Intent> vpnPermissionLauncher;
private ActivityResultLauncher<String> requestPermissionLauncher; private ActivityResultLauncher<String[]> requestPermissionsLauncher;
private ActivityResultLauncher<String> notificationPermissionLauncher;
private boolean isReadingLogs = false; private boolean isReadingLogs = false;
private Handler sizeUpdateHandler = new Handler(); private final Handler sizeUpdateHandler = new Handler();
private Runnable sizeUpdateRunnable; private Runnable sizeUpdateRunnable;
private final BroadcastReceiver logReceiver = new BroadcastReceiver() { private final BroadcastReceiver logReceiver = new BroadcastReceiver() {
@ -120,38 +122,35 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
prefs = new Preferences(this); prefs = new Preferences(this);
setContentView(R.layout.main); setContentView(R.layout.main);
// Initialize Result Launchers // 1. Initialize Result Launchers
vpnPermissionLauncher = registerForActivityResult( vpnPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), new ActivityResultContracts.StartActivityForResult(),
result -> { result -> {
if (result.getResultCode() == RESULT_OK && prefs.getEnable()) { if (result.getResultCode() == RESULT_OK && prefs.getEnable()) {
connectVpn(); connectVpn();
} }
// Chain: Check battery after VPN dialog
checkBatteryOptimizations();
} }
); );
requestPermissionLauncher = registerForActivityResult( // Modern ordered permission requester
new ActivityResultContracts.RequestPermission(), requestPermissionsLauncher = registerForActivityResult(
isGranted -> { new ActivityResultContracts.RequestMultiplePermissions(),
if (isGranted) { result -> {
addToLog("Termux RUN_COMMAND permission granted"); for (Map.Entry<String, Boolean> entry : result.entrySet()) {
} else { if (entry.getKey().equals(TERMUX_PERMISSION)) {
addToLog("Termux permission denied. Watchdog stimulus may fail."); addToLog(entry.getValue() ? "Termux permission granted" : "Termux permission denied");
} } else if (entry.getKey().equals(Manifest.permission.POST_NOTIFICATIONS)) {
} addToLog(entry.getValue() ? "Notification permission granted" : "Notification permission denied");
); }
notificationPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
isGranted -> {
if (isGranted) {
addToLog("Notification permission granted");
} else {
addToLog("Notification permission denied. Status visibility may be limited.");
} }
// Step 2: After system permissions, request VPN permission
prepareVpn();
} }
); );
// UI Bindings
edittext_socks_addr = findViewById(R.id.socks_addr); edittext_socks_addr = findViewById(R.id.socks_addr);
edittext_socks_udp_addr = findViewById(R.id.socks_udp_addr); edittext_socks_udp_addr = findViewById(R.id.socks_udp_addr);
edittext_socks_port = findViewById(R.id.socks_port); edittext_socks_port = findViewById(R.id.socks_port);
@ -167,25 +166,40 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
button_apps = findViewById(R.id.apps); button_apps = findViewById(R.id.apps);
button_save = findViewById(R.id.save); button_save = findViewById(R.id.save);
button_control = findViewById(R.id.control); button_control = findViewById(R.id.control);
watchdogControl = findViewById(R.id.watchdog_control); watchdogControl = findViewById(R.id.watchdog_control);
watchdogControl.setOnClickListener(this);
logActions = findViewById(R.id.log_actions); logActions = findViewById(R.id.log_actions);
Button btnClearLog = findViewById(R.id.btn_clear_log); Button btnClearLog = findViewById(R.id.btn_clear_log);
Button btnCopyLog = findViewById(R.id.btn_copy_log); Button btnCopyLog = findViewById(R.id.btn_copy_log);
btnClearLog.setOnClickListener(this);
btnCopyLog.setOnClickListener(this);
connectionLog = findViewById(R.id.connection_log); connectionLog = findViewById(R.id.connection_log);
connectionLog.setMovementMethod(new ScrollingMovementMethod());
connectionLog.setTextIsSelectable(true);
logProgress = findViewById(R.id.log_progress); logProgress = findViewById(R.id.log_progress);
logWarning = findViewById(R.id.log_warning_text); logWarning = findViewById(R.id.log_warning_text);
logSizeText = findViewById(R.id.log_size_text); logSizeText = findViewById(R.id.log_size_text);
themeToggle = findViewById(R.id.theme_toggle);
versionFooter = findViewById(R.id.version_text);
configLayout = findViewById(R.id.config_layout);
configLabel = findViewById(R.id.config_label);
advancedConfig = findViewById(R.id.advanced_config);
advConfigLabel = findViewById(R.id.adv_config_label);
logLabel = findViewById(R.id.log_label);
// Allow internal scrolling by disabling parent intercept // Listeners
watchdogControl.setOnClickListener(this);
btnClearLog.setOnClickListener(this);
btnCopyLog.setOnClickListener(this);
themeToggle.setOnClickListener(v -> toggleTheme());
configLabel.setOnClickListener(v -> toggleVisibility(configLayout, configLabel, getString(R.string.configuration_label)));
advConfigLabel.setOnClickListener(v -> toggleVisibility(advancedConfig, advConfigLabel, getString(R.string.advanced_settings_label)));
logLabel.setOnClickListener(v -> handleLogToggle());
checkbox_udp_in_tcp.setOnClickListener(this);
checkbox_remote_dns.setOnClickListener(this);
checkbox_global.setOnClickListener(this);
button_apps.setOnClickListener(this);
button_save.setOnClickListener(this);
button_control.setOnClickListener(this);
connectionLog.setMovementMethod(new ScrollingMovementMethod());
connectionLog.setTextIsSelectable(true);
connectionLog.setOnTouchListener((v, event) -> { connectionLog.setOnTouchListener((v, event) -> {
v.getParent().requestDisallowInterceptTouchEvent(true); v.getParent().requestDisallowInterceptTouchEvent(true);
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
@ -194,55 +208,12 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
return false; return false;
}); });
configLayout = findViewById(R.id.config_layout);
configLabel = findViewById(R.id.config_label);
configLabel.setOnClickListener(v -> toggleVisibility(configLayout, configLabel, getString(R.string.configuration_label)));
advancedConfig = findViewById(R.id.advanced_config);
advConfigLabel = findViewById(R.id.adv_config_label);
advConfigLabel.setOnClickListener(v -> toggleVisibility(advancedConfig, advConfigLabel, getString(R.string.advanced_settings_label)));
logLabel = findViewById(R.id.log_label);
logLabel.setOnClickListener(v -> {
boolean isOpening = connectionLog.getVisibility() == View.GONE;
if (isOpening) {
readBlackBoxLogs();
startLogSizeUpdates();
} else {
stopLogSizeUpdates();
}
toggleVisibility(connectionLog, logLabel, getString(R.string.connection_log_label));
logActions.setVisibility(connectionLog.getVisibility());
if (logSizeText != null) logSizeText.setVisibility(connectionLog.getVisibility());
});
themeToggle = findViewById(R.id.theme_toggle);
themeToggle.setOnClickListener(v -> toggleTheme());
applySavedTheme(); applySavedTheme();
versionFooter = findViewById(R.id.version_text);
setVersionFooter(); setVersionFooter();
checkbox_udp_in_tcp.setOnClickListener(this);
checkbox_remote_dns.setOnClickListener(this);
checkbox_global.setOnClickListener(this);
button_apps.setOnClickListener(this);
button_save.setOnClickListener(this);
button_control.setOnClickListener(this);
updateUI(); updateUI();
/* Request permissions */ // Start sequential permission chain
checkNotificationPermission(); initiatePermissionChain();
checkTermuxPermission();
checkBatteryOptimizations();
/* Request VPN permission */
Intent intent = VpnService.prepare(MainActivity.this);
if (intent != null) {
vpnPermissionLauncher.launch(intent);
} else if (prefs.getEnable()) {
connectVpn();
}
addToLog(getString(R.string.app_started)); addToLog(getString(R.string.app_started));
@ -255,20 +226,47 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
}; };
} }
private void checkNotificationPermission() { private void initiatePermissionChain() {
List<String> permissions = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); permissions.add(Manifest.permission.POST_NOTIFICATIONS);
} }
} }
if (ContextCompat.checkSelfPermission(this, TERMUX_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
permissions.add(TERMUX_PERMISSION);
}
if (!permissions.isEmpty()) {
requestPermissionsLauncher.launch(permissions.toArray(new String[0]));
} else {
prepareVpn();
}
} }
private void checkTermuxPermission() { private void prepareVpn() {
if (ContextCompat.checkSelfPermission(this, TERMUX_PERMISSION) != PackageManager.PERMISSION_GRANTED) { Intent intent = VpnService.prepare(MainActivity.this);
requestPermissionLauncher.launch(TERMUX_PERMISSION); if (intent != null) {
vpnPermissionLauncher.launch(intent);
} else {
if (prefs.getEnable()) connectVpn();
checkBatteryOptimizations();
} }
} }
private void handleLogToggle() {
boolean isOpening = connectionLog.getVisibility() == View.GONE;
if (isOpening) {
readBlackBoxLogs();
startLogSizeUpdates();
} else {
stopLogSizeUpdates();
}
toggleVisibility(connectionLog, logLabel, getString(R.string.connection_log_label));
logActions.setVisibility(connectionLog.getVisibility());
if (logSizeText != null) logSizeText.setVisibility(connectionLog.getVisibility());
}
private void startLogSizeUpdates() { private void startLogSizeUpdates() {
sizeUpdateHandler.removeCallbacks(sizeUpdateRunnable); sizeUpdateHandler.removeCallbacks(sizeUpdateRunnable);
sizeUpdateHandler.post(sizeUpdateRunnable); sizeUpdateHandler.post(sizeUpdateRunnable);
@ -302,10 +300,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
private void readBlackBoxLogs() { private void readBlackBoxLogs() {
if (isReadingLogs) return; if (isReadingLogs) return;
isReadingLogs = true; isReadingLogs = true;
if (logProgress != null) logProgress.setVisibility(View.VISIBLE);
if (logProgress != null) {
logProgress.setVisibility(View.VISIBLE);
}
new Thread(() -> { new Thread(() -> {
File logFile = new File(getFilesDir(), "watchdog_heartbeat_log.txt"); File logFile = new File(getFilesDir(), "watchdog_heartbeat_log.txt");
@ -327,8 +322,6 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
} }
final String result = sb.toString(); final String result = sb.toString();
// Check rapid growth flag
SharedPreferences internalPrefs = getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE); SharedPreferences internalPrefs = getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
final boolean isRapid = internalPrefs.getBoolean(IIABWatchdog.PREF_RAPID_GROWTH, false); final boolean isRapid = internalPrefs.getBoolean(IIABWatchdog.PREF_RAPID_GROWTH, false);
@ -389,13 +382,20 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) { 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")) {
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) new AlertDialog.Builder(this)
.setTitle(R.string.battery_opt_title) .setTitle(R.string.battery_opt_title)
.setMessage(R.string.battery_opt_msg) .setMessage(message)
.setPositiveButton(R.string.go_to_settings, (dialog, which) -> { .setPositiveButton(R.string.go_to_settings, (dialog, which) -> {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); openBatterySettings(manufacturer);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}) })
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show(); .show();
@ -403,6 +403,42 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
} }
} }
private void openBatterySettings(String manufacturer) {
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);
return;
} catch (Exception e) {
try {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.coloros.oppoguardelf",
"com.coloros.oppoguardelf.Permission.BackgroundAllowAppListActivity"));
startActivity(intent);
return;
} 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);
return;
} catch (Exception e) {
}
}
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
private void toggleTheme() { private void toggleTheme() {
SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE); SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
int currentMode = AppCompatDelegate.getDefaultNightMode(); int currentMode = AppCompatDelegate.getDefaultNightMode();
@ -455,7 +491,9 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
IntentFilter filter = new IntentFilter(IIABWatchdog.ACTION_LOG_MESSAGE); IntentFilter filter = new IntentFilter(IIABWatchdog.ACTION_LOG_MESSAGE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
registerReceiver(logReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(logReceiver, filter, Context.RECEIVER_NOT_EXPORTED); registerReceiver(logReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else { } else {
registerReceiver(logReceiver, filter); registerReceiver(logReceiver, filter);
@ -512,15 +550,12 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
private void resetLogFile() { private void resetLogFile() {
File logFile = new File(getFilesDir(), "watchdog_heartbeat_log.txt"); File logFile = new File(getFilesDir(), "watchdog_heartbeat_log.txt");
try (PrintWriter pw = new PrintWriter(logFile)) { try (PrintWriter pw = new PrintWriter(logFile)) {
pw.print(""); // Truncate file to 0 bytes pw.print("");
connectionLog.setText(""); connectionLog.setText("");
addToLog(getString(R.string.log_reset_user)); addToLog(getString(R.string.log_reset_user));
// Clear rapid growth warning
SharedPreferences internalPrefs = getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE); SharedPreferences internalPrefs = getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
internalPrefs.edit().putBoolean(IIABWatchdog.PREF_RAPID_GROWTH, false).apply(); internalPrefs.edit().putBoolean(IIABWatchdog.PREF_RAPID_GROWTH, false).apply();
if (logWarning != null) logWarning.setVisibility(View.GONE); if (logWarning != null) logWarning.setVisibility(View.GONE);
updateLogSizeUI(); updateLogSizeUI();
Toast.makeText(this, R.string.log_cleared_toast, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.log_cleared_toast, Toast.LENGTH_SHORT).show();
} catch (IOException e) { } catch (IOException e) {
@ -560,16 +595,13 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
showBiometricPrompt(); showBiometricPrompt();
} else { } else {
BiometricManager biometricManager = BiometricManager.from(this); BiometricManager biometricManager = BiometricManager.from(this);
int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL; int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK | BiometricManager.Authenticators.DEVICE_CREDENTIAL; authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
} }
boolean isSecure = false; boolean isSecure = false;
android.app.KeyguardManager km = (android.app.KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); android.app.KeyguardManager km = (android.app.KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
if (km != null && km.isDeviceSecure()) isSecure = true; if (km != null && km.isDeviceSecure()) isSecure = true;
if (biometricManager.canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS || isSecure) { if (biometricManager.canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS || isSecure) {
addToLog(getString(R.string.user_initiated_conn)); addToLog(getString(R.string.user_initiated_conn));
toggleService(false); toggleService(false);
@ -602,15 +634,12 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
toggleService(true); toggleService(true);
} }
}); });
int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL; int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder() BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.auth_required_title)) .setTitle(getString(R.string.auth_required_title))
.setSubtitle(getString(R.string.auth_required_subtitle)) .setSubtitle(getString(R.string.auth_required_subtitle))
.setAllowedAuthenticators(authenticators) .setAllowedAuthenticators(authenticators)
.build(); .build();
biometricPrompt.authenticate(promptInfo); biometricPrompt.authenticate(promptInfo);
} }
@ -623,15 +652,12 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
toggleWatchdog(true); toggleWatchdog(true);
} }
}); });
int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL; int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder() BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.unlock_watchdog_title)) .setTitle(getString(R.string.unlock_watchdog_title))
.setSubtitle(getString(R.string.unlock_watchdog_subtitle)) .setSubtitle(getString(R.string.unlock_watchdog_subtitle))
.setAllowedAuthenticators(authenticators) .setAllowedAuthenticators(authenticators)
.build(); .build();
biometricPrompt.authenticate(promptInfo); biometricPrompt.authenticate(promptInfo);
} }
@ -652,7 +678,6 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
private void updateUI() { private void updateUI() {
boolean vpnActive = prefs.getEnable(); boolean vpnActive = prefs.getEnable();
boolean watchdogActive = prefs.getWatchdogEnable(); boolean watchdogActive = prefs.getWatchdogEnable();
if (vpnActive) { if (vpnActive) {
button_control.setText(R.string.control_disable); button_control.setText(R.string.control_disable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_on)); button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_on));
@ -660,7 +685,6 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
button_control.setText(R.string.control_enable); button_control.setText(R.string.control_enable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_off)); button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_off));
} }
if (watchdogActive) { if (watchdogActive) {
watchdogControl.setText(R.string.watchdog_disable); watchdogControl.setText(R.string.watchdog_disable);
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on)); watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on));
@ -668,7 +692,6 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
watchdogControl.setText(R.string.watchdog_enable); watchdogControl.setText(R.string.watchdog_enable);
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_off)); watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_off));
} }
edittext_socks_addr.setText(prefs.getSocksAddress()); edittext_socks_addr.setText(prefs.getSocksAddress());
edittext_socks_udp_addr.setText(prefs.getSocksUdpAddress()); edittext_socks_udp_addr.setText(prefs.getSocksUdpAddress());
edittext_socks_port.setText(String.valueOf(prefs.getSocksPort())); edittext_socks_port.setText(String.valueOf(prefs.getSocksPort()));
@ -681,7 +704,6 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
checkbox_global.setChecked(prefs.getGlobal()); checkbox_global.setChecked(prefs.getGlobal());
checkbox_udp_in_tcp.setChecked(prefs.getUdpInTcp()); checkbox_udp_in_tcp.setChecked(prefs.getUdpInTcp());
checkbox_remote_dns.setChecked(prefs.getRemoteDns()); checkbox_remote_dns.setChecked(prefs.getRemoteDns());
boolean editable = !vpnActive; boolean editable = !vpnActive;
edittext_socks_addr.setEnabled(editable); edittext_socks_addr.setEnabled(editable);
edittext_socks_port.setEnabled(editable); edittext_socks_port.setEnabled(editable);

View File

@ -76,4 +76,8 @@
<string name="termux_pulse_error">[Termux] Pulse Error (exit %1$d): %2$s</string> <string name="termux_pulse_error">[Termux] Pulse Error (exit %1$d): %2$s</string>
<string name="log_size_format">Size: %1$s / 10MB</string> <string name="log_size_format">Size: %1$s / 10MB</string>
<!-- Brand specific battery warnings -->
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme detected: Please ensure you also enable \'Allow background activity\' in this app\'s settings.</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi detected: Please set battery saver to \'No restrictions\' for this app.</string>
</resources> </resources>