diff --git a/apk/controller/app/src/main/java/org/iiab/controller/MainActivity.java b/apk/controller/app/src/main/java/org/iiab/controller/MainActivity.java index 51d2c93..84b5bfa 100644 --- a/apk/controller/app/src/main/java/org/iiab/controller/MainActivity.java +++ b/apk/controller/app/src/main/java/org/iiab/controller/MainActivity.java @@ -636,11 +636,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe 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 { - registerReceiver(logReceiver, filter); - } + ContextCompat.registerReceiver(this, logReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED); } @Override diff --git a/apk/controller/app/src/main/java/org/iiab/controller/SetupActivity.java b/apk/controller/app/src/main/java/org/iiab/controller/SetupActivity.java index 6ea6d4a..9518241 100644 --- a/apk/controller/app/src/main/java/org/iiab/controller/SetupActivity.java +++ b/apk/controller/app/src/main/java/org/iiab/controller/SetupActivity.java @@ -11,6 +11,9 @@ import android.os.Build; import android.os.Bundle; import android.os.PowerManager; 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.TextView; @@ -19,6 +22,7 @@ import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.SwitchCompat; import androidx.core.content.ContextCompat; +import com.google.android.material.snackbar.Snackbar; public class SetupActivity extends AppCompatActivity { @@ -26,6 +30,9 @@ public class SetupActivity extends AppCompatActivity { private SwitchCompat switchNotif, switchTermux, switchVpn, switchBattery; private Button btnContinue; + private Button btnManageAll; + private Button btnTermuxOverlay; + private Button btnManageTermux; private ActivityResultLauncher requestPermissionLauncher; private ActivityResultLauncher vpnLauncher; @@ -44,6 +51,9 @@ public class SetupActivity extends AppCompatActivity { switchVpn = findViewById(R.id.switch_perm_vpn); switchBattery = findViewById(R.id.switch_perm_battery); 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 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { @@ -82,22 +92,34 @@ public class SetupActivity extends AppCompatActivity { private void setupListeners() { switchNotif.setOnClickListener(v -> { + if (hasNotifPermission()) { + handleRevokeAttempt(v); + return; + } if (switchNotif.isChecked()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); } } - switchNotif.setChecked(hasNotifPermission()); // Revert visual state pending system response + switchNotif.setChecked(false); // Force visual state back until system confirms }); switchTermux.setOnClickListener(v -> { + if (hasTermuxPermission()) { + handleRevokeAttempt(v); + return; + } if (switchTermux.isChecked()) { requestPermissionLauncher.launch(TERMUX_PERMISSION); } - switchTermux.setChecked(hasTermuxPermission()); + switchTermux.setChecked(false); }); switchVpn.setOnClickListener(v -> { + if (hasVpnPermission()) { + handleRevokeAttempt(v); + return; + } if (switchVpn.isChecked()) { Intent intent = VpnService.prepare(this); if (intent != null) { @@ -106,10 +128,14 @@ public class SetupActivity extends AppCompatActivity { checkAllPermissions(); // Already granted } } - switchVpn.setChecked(hasVpnPermission()); + switchVpn.setChecked(false); }); switchBattery.setOnClickListener(v -> { + if (hasBatteryPermission()) { + handleRevokeAttempt(v); + return; + } if (switchBattery.isChecked()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); @@ -117,7 +143,30 @@ public class SetupActivity extends AppCompatActivity { 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 } + /** + * 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() { boolean notif = hasNotifPermission(); boolean termux = hasTermuxPermission(); @@ -138,12 +210,6 @@ public class SetupActivity extends AppCompatActivity { switchVpn.setChecked(vpn); switchBattery.setChecked(battery); - // Lock switches if already granted to prevent user confusion - if (notif) switchNotif.setEnabled(false); - if (termux) switchTermux.setEnabled(false); - if (vpn) switchVpn.setEnabled(false); - if (battery) switchBattery.setEnabled(false); - boolean allGranted = termux && vpn && battery; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { allGranted = allGranted && notif; diff --git a/apk/controller/app/src/main/res/anim/shake.xml b/apk/controller/app/src/main/res/anim/shake.xml new file mode 100644 index 0000000..05e4e1b --- /dev/null +++ b/apk/controller/app/src/main/res/anim/shake.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + diff --git a/apk/controller/app/src/main/res/layout/activity_setup.xml b/apk/controller/app/src/main/res/layout/activity_setup.xml index 7dfa601..1f71cf5 100644 --- a/apk/controller/app/src/main/res/layout/activity_setup.xml +++ b/apk/controller/app/src/main/res/layout/activity_setup.xml @@ -38,7 +38,8 @@ android:textColor="?android:attr/textColorPrimary" android:textSize="16sp" android:paddingVertical="12dp" - android:layout_marginBottom="8dp"/> + android:layout_marginBottom="8dp" + android:theme="@style/PurpleSwitchTheme"/> + android:layout_marginBottom="8dp" + android:theme="@style/PurpleSwitchTheme"/> + android:layout_marginBottom="8dp" + android:theme="@style/PurpleSwitchTheme"/> + +