-
Notifications
You must be signed in to change notification settings - Fork 102
Added auto switch back like described in issue #59 #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ | |
| <uses-permission | ||
| android:name="android.permission.WRITE_SECURE_SETTINGS" | ||
| tools:ignore="ProtectedPermissions" /> | ||
| <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> | ||
| <uses-permission android:name="android.permission.USE_EXACT_ALARM" /> | ||
|
|
||
| <application | ||
| android:name=".PrivateDNSApp" | ||
|
|
@@ -79,6 +81,9 @@ | |
| <action android:name="android.service.quicksettings.action.QS_TILE" /> | ||
| </intent-filter> | ||
| </service> | ||
| <receiver | ||
| android:name=".service.RevertReceiver" | ||
| android:exported="true" /> | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason for the receiver being exported? It does not have to be to handle calls from the same package. |
||
| </application> | ||
|
|
||
| </manifest> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| package ru.karasevm.privatednstoggle.service | ||
|
|
||
| import android.content.BroadcastReceiver | ||
| import android.content.Context | ||
| import android.content.Intent | ||
| import android.util.Log | ||
| import ru.karasevm.privatednstoggle.util.PreferenceHelper | ||
| import ru.karasevm.privatednstoggle.util.PreferenceHelper.revertMode | ||
| import ru.karasevm.privatednstoggle.util.PreferenceHelper.revertProvider | ||
| import ru.karasevm.privatednstoggle.util.PrivateDNSUtils | ||
| import ru.karasevm.privatednstoggle.util.PreferenceHelper.revertScheduledAt | ||
| import android.widget.Toast | ||
| import android.app.NotificationManager | ||
| import android.app.NotificationChannel | ||
| import androidx.core.app.NotificationCompat | ||
|
|
||
|
|
||
| class RevertReceiver : BroadcastReceiver() { | ||
| companion object { | ||
| private const val TAG = "RevertReceiver" | ||
| } | ||
|
|
||
| private fun showNotification(context: Context, message: String) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The app targets the latest recommended sdk, please read the notification api reference and ensure it works properly on modern versions of android. Don't request the notification permission unless user explicitly turns on the revert feature. |
||
| try { | ||
| val channelId = "revert_debug" | ||
| val notificationId = 12345 | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace the placeholder IDs. |
||
|
|
||
| val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager | ||
|
|
||
| // Create notification channel for Android 8+ | ||
| if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Private DNS feature was added in A9, minSdk is 28, this check is useless. |
||
| val channel = NotificationChannel(channelId, "Revert Debug", NotificationManager.IMPORTANCE_HIGH) | ||
| notificationManager.createNotificationChannel(channel) | ||
| } | ||
|
|
||
| val notification = NotificationCompat.Builder(context, channelId) | ||
| .setContentTitle("DNS Revert") | ||
| .setContentText(message) | ||
| .setSmallIcon(android.R.drawable.ic_dialog_info) | ||
| .setPriority(NotificationCompat.PRIORITY_HIGH) | ||
| .setAutoCancel(true) | ||
| .build() | ||
|
|
||
| notificationManager.notify(notificationId, notification) | ||
| } catch (e: Exception) { | ||
| Log.e(TAG, "Failed to show notification: ${e.message}") | ||
| } | ||
| } | ||
|
|
||
| override fun onReceive(context: Context, intent: Intent) { | ||
| Log.d(TAG, "onReceive: revert alarm fired - ENTERING BROADCAST RECEIVER") | ||
|
|
||
| val prefs = PreferenceHelper.defaultPreference(context) | ||
| val mode = prefs.revertMode | ||
| val provider = prefs.revertProvider | ||
| val scheduledAt = prefs.revertScheduledAt | ||
|
|
||
| val logMsg = "onReceive: stored revert mode=$mode provider=$provider scheduledAt=$scheduledAt now=${System.currentTimeMillis()}" | ||
| Log.d(TAG, logMsg) | ||
|
|
||
| if (mode.isNullOrBlank()) { | ||
| Log.d(TAG, "onReceive: nothing to revert") | ||
| return | ||
| } | ||
|
|
||
| // Apply provider first if private | ||
| if (mode.equals(PrivateDNSUtils.DNS_MODE_PRIVATE, true)) { | ||
| PrivateDNSUtils.setPrivateProvider(context.contentResolver, if (provider.isNullOrBlank()) null else provider) | ||
| } else { | ||
| // when reverting to non-private, preserve provider value | ||
| PrivateDNSUtils.setPrivateProvider(context.contentResolver, provider) | ||
| } | ||
|
|
||
| PrivateDNSUtils.setPrivateMode(context.contentResolver, mode) | ||
|
|
||
| val revertMsg = "Private DNS reverted to $mode" | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't hardcode strings. If you need formatting, see |
||
|
|
||
| // Show notification (persistent & visible) | ||
| showNotification(context, revertMsg) | ||
|
|
||
| // Notify user via toast for debugging | ||
| try { | ||
| Toast.makeText(context, revertMsg, Toast.LENGTH_LONG).show() | ||
| } catch (e: Exception) { | ||
| Log.w(TAG, "onReceive: failed to show toast: ${e.message}") | ||
| } | ||
|
|
||
| // clear saved revert info | ||
| prefs.revertMode = null | ||
| prefs.revertProvider = null | ||
|
|
||
| val finalMsg = "onReceive: reverted to mode=$mode provider=$provider" | ||
| Log.d(TAG, finalMsg) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| package ru.karasevm.privatednstoggle.service | ||
|
|
||
| import android.app.AlarmManager | ||
| import android.app.PendingIntent | ||
| import android.content.Context | ||
| import android.content.Intent | ||
| import android.os.SystemClock | ||
| import ru.karasevm.privatednstoggle.util.PreferenceHelper | ||
| import java.util.concurrent.TimeUnit | ||
| import ru.karasevm.privatednstoggle.util.PreferenceHelper.revertScheduledAt | ||
| import android.util.Log | ||
|
|
||
| object RevertScheduler { | ||
| private const val TAG = "RevertScheduler" | ||
| private const val ACTION_REVERT = "ru.karasevm.privatednstoggle.ACTION_REVERT" | ||
|
|
||
| fun scheduleRevert(context: Context, minutes: Int) { | ||
| val prefs = PreferenceHelper.defaultPreference(context) | ||
|
|
||
| val logMsg1 = "scheduleRevert: CALLED with minutes=$minutes" | ||
| Log.d(TAG, logMsg1) | ||
|
|
||
| // Build intent to fire our RevertReceiver | ||
| val intent = Intent(context, RevertReceiver::class.java).apply { | ||
| action = ACTION_REVERT | ||
| setPackage(context.packageName) | ||
| } | ||
|
|
||
| val pending = PendingIntent.getBroadcast( | ||
| context, | ||
| 0, | ||
| intent, | ||
| PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE | ||
| ) | ||
|
|
||
| val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager | ||
|
|
||
| // Use elapsed realtime + minutes | ||
| val triggerAt = SystemClock.elapsedRealtime() + TimeUnit.MINUTES.toMillis(minutes.toLong()) | ||
| val triggerAtWall = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(minutes.toLong()) | ||
|
|
||
| val logMsg2 = "scheduleRevert: setting alarm triggerAt(elapsed)=$triggerAt triggerAt(wall)=$triggerAtWall" | ||
| Log.d(TAG, logMsg2) | ||
|
|
||
| try { | ||
| alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAt, pending) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See comment in the manifest, replace either with set or setAndAllowWhileIdle. |
||
|
|
||
| val logMsg3 = "scheduleRevert: alarm SET SUCCESSFULLY" | ||
| Log.d(TAG, logMsg3) | ||
| } catch (e: Exception) { | ||
| val logMsg3 = "scheduleRevert: ERROR setting alarm: ${e.message}" | ||
| Log.e(TAG, logMsg3) | ||
| } | ||
|
|
||
| // Persist scheduled time for debugging | ||
| prefs.revertScheduledAt = triggerAtWall | ||
| val logMsg4 = "scheduleRevert: persisted scheduled_at=$triggerAtWall" | ||
| Log.d(TAG, logMsg4) | ||
| } | ||
|
|
||
| fun cancelRevert(context: Context) { | ||
| val logMsg1 = "cancelRevert: CALLED" | ||
| Log.d(TAG, logMsg1) | ||
|
|
||
| val intent = Intent(context, RevertReceiver::class.java).apply { | ||
| action = ACTION_REVERT | ||
| setPackage(context.packageName) | ||
| } | ||
| val pending = PendingIntent.getBroadcast( | ||
| context, | ||
| 0, | ||
| intent, | ||
| PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE | ||
| ) | ||
| val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager | ||
| alarmManager.cancel(pending) | ||
| val prefs = PreferenceHelper.defaultPreference(context) | ||
| prefs.revertScheduledAt = 0L | ||
| val logMsg2 = "cancelRevert: CANCELLED pending revert" | ||
| Log.d(TAG, logMsg2) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ import ru.karasevm.privatednstoggle.databinding.DialogOptionsBinding | |
| import ru.karasevm.privatednstoggle.util.PreferenceHelper | ||
| import ru.karasevm.privatednstoggle.util.PreferenceHelper.autoMode | ||
| import ru.karasevm.privatednstoggle.util.PreferenceHelper.requireUnlock | ||
| import ru.karasevm.privatednstoggle.util.PreferenceHelper.autoRevertEnabled | ||
| import ru.karasevm.privatednstoggle.util.PreferenceHelper.autoRevertMinutes | ||
| import ru.karasevm.privatednstoggle.util.PrivateDNSUtils | ||
|
|
||
| class OptionsDialogFragment : DialogFragment() { | ||
|
|
@@ -59,5 +61,30 @@ class OptionsDialogFragment : DialogFragment() { | |
| binding.requireUnlockSwitch.setOnCheckedChangeListener { _, isChecked -> | ||
| sharedPreferences.requireUnlock = isChecked | ||
| } | ||
|
|
||
| val autoRevertEnabled = sharedPreferences.autoRevertEnabled | ||
| binding.autoRevertSwitch.isChecked = autoRevertEnabled | ||
| binding.autoRevertSwitch.setOnCheckedChangeListener { _, isChecked -> | ||
| sharedPreferences.autoRevertEnabled = isChecked | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably would be good to cleanup any active alarms if user disables the feature. |
||
| } | ||
|
|
||
| val minutes = sharedPreferences.autoRevertMinutes | ||
| binding.autoRevertMinutesInput.setText(minutes.toString()) | ||
| binding.autoRevertMinutesInput.setOnFocusChangeListener { _, hasFocus -> | ||
| if (!hasFocus) { | ||
| val text = binding.autoRevertMinutesInput.text?.toString() ?: "" | ||
| val value = text.toIntOrNull() ?: minutes | ||
| val finalValue = if (value <= 0) 1 else value | ||
| sharedPreferences.autoRevertMinutes = finalValue | ||
| binding.autoRevertMinutesInput.setText(finalValue.toString()) | ||
| } | ||
| } | ||
| // Also save on dialog dismiss to catch any pending edits | ||
| dialog?.setOnDismissListener { | ||
| val text = binding.autoRevertMinutesInput.text?.toString() ?: "" | ||
| val value = text.toIntOrNull() ?: minutes | ||
| val finalValue = if (value <= 0) 1 else value | ||
| sharedPreferences.autoRevertMinutes = finalValue | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer not using restricted permissions. To quote the API reference "This is only intended for use by apps that rely on exact alarms for their core functionality. You should continue using SCHEDULE_EXACT_ALARM if your app needs exact alarms for a secondary feature that users may or may not use within your app."