[controller] mejorar tabla de permismos

This commit is contained in:
Luis Guzmán 2026-04-03 11:25:17 -06:00
parent d393748393
commit a041c433f9
6 changed files with 175 additions and 20 deletions

View File

@ -636,11 +636,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
filter.addAction(WatchdogService.ACTION_STATE_STARTED); filter.addAction(WatchdogService.ACTION_STATE_STARTED);
filter.addAction(WatchdogService.ACTION_STATE_STOPPED); filter.addAction(WatchdogService.ACTION_STATE_STOPPED);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { ContextCompat.registerReceiver(this, logReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);
registerReceiver(logReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
registerReceiver(logReceiver, filter);
}
} }
@Override @Override

View File

@ -11,6 +11,9 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.PowerManager; import android.os.PowerManager;
import android.provider.Settings; import android.provider.Settings;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
@ -19,6 +22,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.SwitchCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.google.android.material.snackbar.Snackbar;
public class SetupActivity extends AppCompatActivity { public class SetupActivity extends AppCompatActivity {
@ -26,6 +30,9 @@ public class SetupActivity extends AppCompatActivity {
private SwitchCompat switchNotif, switchTermux, switchVpn, switchBattery; private SwitchCompat switchNotif, switchTermux, switchVpn, switchBattery;
private Button btnContinue; private Button btnContinue;
private Button btnManageAll;
private Button btnTermuxOverlay;
private Button btnManageTermux;
private ActivityResultLauncher<String> requestPermissionLauncher; private ActivityResultLauncher<String> requestPermissionLauncher;
private ActivityResultLauncher<Intent> vpnLauncher; private ActivityResultLauncher<Intent> vpnLauncher;
@ -44,6 +51,9 @@ public class SetupActivity extends AppCompatActivity {
switchVpn = findViewById(R.id.switch_perm_vpn); switchVpn = findViewById(R.id.switch_perm_vpn);
switchBattery = findViewById(R.id.switch_perm_battery); switchBattery = findViewById(R.id.switch_perm_battery);
btnContinue = findViewById(R.id.btn_setup_continue); btnContinue = findViewById(R.id.btn_setup_continue);
btnManageAll = findViewById(R.id.btn_manage_all);
btnTermuxOverlay = findViewById(R.id.btn_termux_overlay);
btnManageTermux = findViewById(R.id.btn_manage_termux);
// Hide Notification switch if Android < 13 // Hide Notification switch if Android < 13
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
@ -82,22 +92,34 @@ public class SetupActivity extends AppCompatActivity {
private void setupListeners() { private void setupListeners() {
switchNotif.setOnClickListener(v -> { switchNotif.setOnClickListener(v -> {
if (hasNotifPermission()) {
handleRevokeAttempt(v);
return;
}
if (switchNotif.isChecked()) { if (switchNotif.isChecked()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
} }
} }
switchNotif.setChecked(hasNotifPermission()); // Revert visual state pending system response switchNotif.setChecked(false); // Force visual state back until system confirms
}); });
switchTermux.setOnClickListener(v -> { switchTermux.setOnClickListener(v -> {
if (hasTermuxPermission()) {
handleRevokeAttempt(v);
return;
}
if (switchTermux.isChecked()) { if (switchTermux.isChecked()) {
requestPermissionLauncher.launch(TERMUX_PERMISSION); requestPermissionLauncher.launch(TERMUX_PERMISSION);
} }
switchTermux.setChecked(hasTermuxPermission()); switchTermux.setChecked(false);
}); });
switchVpn.setOnClickListener(v -> { switchVpn.setOnClickListener(v -> {
if (hasVpnPermission()) {
handleRevokeAttempt(v);
return;
}
if (switchVpn.isChecked()) { if (switchVpn.isChecked()) {
Intent intent = VpnService.prepare(this); Intent intent = VpnService.prepare(this);
if (intent != null) { if (intent != null) {
@ -106,10 +128,14 @@ public class SetupActivity extends AppCompatActivity {
checkAllPermissions(); // Already granted checkAllPermissions(); // Already granted
} }
} }
switchVpn.setChecked(hasVpnPermission()); switchVpn.setChecked(false);
}); });
switchBattery.setOnClickListener(v -> { switchBattery.setOnClickListener(v -> {
if (hasBatteryPermission()) {
handleRevokeAttempt(v);
return;
}
if (switchBattery.isChecked()) { if (switchBattery.isChecked()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
@ -117,7 +143,30 @@ public class SetupActivity extends AppCompatActivity {
batteryLauncher.launch(intent); batteryLauncher.launch(intent);
} }
} }
switchBattery.setChecked(hasBatteryPermission()); switchBattery.setChecked(false);
});
// Direct access to all the Controller permissions
btnManageAll.setOnClickListener(v -> openAppSettings());
// Direct access to Termux Overlay permissions
btnTermuxOverlay.setOnClickListener(v -> {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:com.termux"));
startActivity(intent);
} catch (Exception e) {
Snackbar.make(v, "Termux is not installed or device not supported.", Snackbar.LENGTH_LONG).show();
}
});
// Direct access to Controller settings (Reuses the method from Phase 1)
btnManageTermux.setOnClickListener(v -> {
try {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:com.termux"));
startActivity(intent);
} catch (Exception e) {
Snackbar.make(v, "Termux is not installed.", Snackbar.LENGTH_LONG).show();
}
}); });
} }
@ -127,6 +176,29 @@ public class SetupActivity extends AppCompatActivity {
checkAllPermissions(); // Refresh state if user returns from settings checkAllPermissions(); // Refresh state if user returns from settings
} }
/**
* It displays visual feedback (shake) and a message when the user
* tries to turn off a permission.
*/
private void handleRevokeAttempt(View switchView) {
// Force the switch to stay checked visually
((SwitchCompat) switchView).setChecked(true);
// Animate the switch (Shake)
Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
switchView.startAnimation(shake);
// Show Snackbar with action to go to Settings
Snackbar.make(findViewById(android.R.id.content), "To revoke permissions, you must do it from system settings.", Snackbar.LENGTH_LONG)
.setAction("SETTINGS", v -> openAppSettings()).show();
}
private void openAppSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
private void checkAllPermissions() { private void checkAllPermissions() {
boolean notif = hasNotifPermission(); boolean notif = hasNotifPermission();
boolean termux = hasTermuxPermission(); boolean termux = hasTermuxPermission();
@ -138,12 +210,6 @@ public class SetupActivity extends AppCompatActivity {
switchVpn.setChecked(vpn); switchVpn.setChecked(vpn);
switchBattery.setChecked(battery); switchBattery.setChecked(battery);
// Lock switches if already granted to prevent user confusion
if (notif) switchNotif.setEnabled(false);
if (termux) switchTermux.setEnabled(false);
if (vpn) switchVpn.setEnabled(false);
if (battery) switchBattery.setEnabled(false);
boolean allGranted = termux && vpn && battery; boolean allGranted = termux && vpn && battery;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
allGranted = allGranted && notif; allGranted = allGranted && notif;

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/overshoot_interpolator"
android:fillAfter="true">
<translate
android:startOffset="0"
android:fromXDelta="0%p"
android:toXDelta="5%p"
android:duration="50" />
<translate
android:startOffset="50"
android:fromXDelta="5%p"
android:toXDelta="-5%p"
android:duration="50" />
<translate
android:startOffset="100"
android:fromXDelta="-5%p"
android:toXDelta="5%p"
android:duration="50" />
<translate
android:startOffset="150"
android:fromXDelta="5%p"
android:toXDelta="-5%p"
android:duration="50" />
<translate
android:startOffset="200"
android:fromXDelta="-5%p"
android:toXDelta="0%p"
android:duration="50" />
</set>

View File

@ -38,7 +38,8 @@
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp" android:textSize="16sp"
android:paddingVertical="12dp" android:paddingVertical="12dp"
android:layout_marginBottom="8dp"/> android:layout_marginBottom="8dp"
android:theme="@style/PurpleSwitchTheme"/>
<androidx.appcompat.widget.SwitchCompat <androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_perm_termux" android:id="@+id/switch_perm_termux"
@ -48,7 +49,8 @@
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp" android:textSize="16sp"
android:paddingVertical="12dp" android:paddingVertical="12dp"
android:layout_marginBottom="8dp"/> android:layout_marginBottom="8dp"
android:theme="@style/PurpleSwitchTheme"/>
<androidx.appcompat.widget.SwitchCompat <androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_perm_vpn" android:id="@+id/switch_perm_vpn"
@ -58,7 +60,8 @@
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp" android:textSize="16sp"
android:paddingVertical="12dp" android:paddingVertical="12dp"
android:layout_marginBottom="8dp"/> android:layout_marginBottom="8dp"
android:theme="@style/PurpleSwitchTheme"/>
<androidx.appcompat.widget.SwitchCompat <androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_perm_battery" android:id="@+id/switch_perm_battery"
@ -68,8 +71,60 @@
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp" android:textSize="16sp"
android:paddingVertical="12dp" android:paddingVertical="12dp"
android:theme="@style/PurpleSwitchTheme"/>
<Button
android:id="@+id/btn_manage_all"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Manage All Permissions"
android:textAllCaps="false"
android:gravity="start|center_vertical"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:paddingVertical="12dp"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:minHeight="0dp"
android:background="?android:attr/selectableItemBackground"
android:layout_marginBottom="12dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Termux custom permissions"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginBottom="8dp"/> android:layout_marginBottom="8dp"/>
<Button
android:id="@+id/btn_termux_overlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Display over other apps"
android:textAllCaps="false"
style="?android:attr/borderlessButtonStyle"
android:gravity="start|center_vertical"
android:textColor="@color/lightGray66" />
<Button
android:id="@+id/btn_manage_termux"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Manage Termux permissions"
android:textAllCaps="false"
style="?android:attr/borderlessButtonStyle"
android:gravity="start|center_vertical"
android:textColor="@color/lightGray66" />
</LinearLayout> </LinearLayout>
<Button <Button

View File

@ -5,7 +5,7 @@
<color name="colorAccent">#2E7D32</color> <color name="colorAccent">#2E7D32</color>
<color name="white">#FFFFFF</color> <color name="white">#FFFFFF</color>
<color name="black">#000000</color> <color name="black">#000000</color>
<color name="lightGray66">#AAAAAA</color>
<color name="background_dark">#121212</color> <color name="background_dark">#121212</color>
<!-- Status Colors (Fixed for readability) --> <!-- Status Colors (Fixed for readability) -->
@ -18,7 +18,7 @@
<color name="btn_vpn_on_dim">#EF9A9A</color> <color name="btn_vpn_on_dim">#EF9A9A</color>
<color name="btn_vpn_off_dim">#A5D6A7</color> <color name="btn_vpn_off_dim">#A5D6A7</color>
<!-- Secciones --> <!-- Sections -->
<color name="section_header_bg">#333333</color> <color name="section_header_bg">#333333</color>
<color name="section_body_bg_light">#F5F5F5</color> <color name="section_body_bg_light">#F5F5F5</color>
<color name="section_body_bg_dark">#1A1A1A</color> <color name="section_body_bg_dark">#1A1A1A</color>

View File

@ -17,4 +17,7 @@
<item name="android:windowNoTitle">true</item> <item name="android:windowNoTitle">true</item>
<item name="android:backgroundDimEnabled">false</item> <item name="android:backgroundDimEnabled">false</item>
</style> </style>
<style name="PurpleSwitchTheme" parent="">
<item name="colorControlActivated">#8A2BE2</item>
</style>
</resources> </resources>