/*
- * Copyright © 2021 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2021-2023 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Cell <https://www.stoutner.com/privacy-cell>.
*
import android.Manifest
import android.content.Intent
import android.content.SharedPreferences
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content.pm.PackageManager
+import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.work.WorkManager
import com.stoutner.privacycell.R
+import com.stoutner.privacycell.activities.PrivacyCellActivity
+import com.stoutner.privacycell.dialogs.NotificationPermissionDialog
import com.stoutner.privacycell.services.RealtimeMonitoringService
+// Define the class constants.
+private const val SCROLL_Y = "scroll_y"
+
class SettingsFragment : PreferenceFragmentCompat() {
// Declare the class variables.
- private lateinit var sharedPreferenceChangeListener: SharedPreferences.OnSharedPreferenceChangeListener
+ private lateinit var sharedPreferenceChangeListener: OnSharedPreferenceChangeListener
// Declare the class views.
private lateinit var realtimeMonitoringPreference: Preference
+ private lateinit var secureNetworkNotificationPreference: Preference
+ private lateinit var insecureNetworkNotificationPreference: Preference
+ private lateinit var antiquatedNetworkNotificationPreference: Preference
+ private lateinit var consider3gAntiquatedPreference: Preference
private lateinit var bottomAppBarPreference: Preference
+ companion object {
+ // Declare the private static class variables. For some reason (I'm looking at you Android's Activity Lifecycle) this only works if these are static.
+ private var fragmentRestarted: Boolean = false
+ private var scrollY: Int = 0
+ }
+
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
// Load the preferences from the XML file.
setPreferencesFromResource(R.xml.preferences, rootKey)
// Get a handle for the shared preferences.
- val sharedPreferences = preferenceScreen.sharedPreferences
+ val sharedPreferences = preferenceScreen.sharedPreferences!!
// Get handles for the preferences.
realtimeMonitoringPreference = findPreference(getString(R.string.realtime_monitoring_key))!!
- val secureNetworkNotificationPreference = findPreference<Preference>(getString(R.string.secure_network_notification_key))!!
- val insecureNetworkNotificationPreference = findPreference<Preference>(getString(R.string.insecure_network_notification_key))!!
+ secureNetworkNotificationPreference = findPreference(getString(R.string.secure_network_notification_key))!!
+ insecureNetworkNotificationPreference = findPreference(getString(R.string.insecure_network_notification_key))!!
+ antiquatedNetworkNotificationPreference = findPreference(getString(R.string.antiquated_network_notification_key))!!
+ consider3gAntiquatedPreference = findPreference(getString(R.string.consider_3g_antiquated_key))!!
bottomAppBarPreference = findPreference(getString(R.string.bottom_app_bar_key))!!
// Only enable the realtime monitoring preference if the READ_PHONE_STATE permission has been granted.
if (realtimeMonitoringPreference.isEnabled) {
// Set the realtime monitoring preference icon.
if (sharedPreferences.getBoolean(getString(R.string.realtime_monitoring_key), false)) {
- // Set the enabled icon.
- realtimeMonitoringPreference.setIcon(R.drawable.realtime_monitoring_enabled)
+ // Set the icons.
+ realtimeMonitoringPreference.setIcon(R.drawable.secure_notification_enabled)
+ secureNetworkNotificationPreference.setIcon(R.drawable.secure_notification_enabled)
+ insecureNetworkNotificationPreference.setIcon(R.drawable.insecure_notification_enabled)
+ antiquatedNetworkNotificationPreference.setIcon(R.drawable.antiquated_notification_enabled)
} else {
- // Set the disabled icon.
- realtimeMonitoringPreference.setIcon(R.drawable.realtime_monitoring_disabled)
+ // Set the icons.
+ realtimeMonitoringPreference.setIcon(R.drawable.secure_notification_disabled)
+ secureNetworkNotificationPreference.setIcon(R.drawable.secure_notification_disabled)
+ insecureNetworkNotificationPreference.setIcon(R.drawable.insecure_notification_disabled)
+ antiquatedNetworkNotificationPreference.setIcon(R.drawable.antiquated_notification_disabled)
}
} else {
- // Set the ghosted icon.
- realtimeMonitoringPreference.setIcon(R.drawable.realtime_monitoring_ghosted)
+ // Set the icons.
+ realtimeMonitoringPreference.setIcon(R.drawable.secure_notification_ghosted)
+ secureNetworkNotificationPreference.setIcon(R.drawable.secure_notification_ghosted)
+ insecureNetworkNotificationPreference.setIcon(R.drawable.insecure_notification_ghosted)
+ antiquatedNetworkNotificationPreference.setIcon(R.drawable.antiquated_notification_ghosted)
}
// Set the notification preferences to depend on the realtime monitoring preference.
secureNetworkNotificationPreference.dependency = getString(R.string.realtime_monitoring_key)
insecureNetworkNotificationPreference.dependency = getString(R.string.realtime_monitoring_key)
+ antiquatedNetworkNotificationPreference.dependency = getString(R.string.realtime_monitoring_key)
// Create the notification intents.
val secureNetworkNotificationIntent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
val insecureNetworkNotificationIntent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
.putExtra(Settings.EXTRA_CHANNEL_ID, RealtimeMonitoringService.INSECURE_NETWORK)
+ val antiquatedNetworkNotificationIntent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
+ .putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
+ .putExtra(Settings.EXTRA_CHANNEL_ID, RealtimeMonitoringService.ANTIQUATED_NETWORK)
// Set the notification preference intents.
secureNetworkNotificationPreference.intent = secureNetworkNotificationIntent
insecureNetworkNotificationPreference.intent = insecureNetworkNotificationIntent
+ antiquatedNetworkNotificationPreference.intent = antiquatedNetworkNotificationIntent
+
+ // Set the consider 3G antiquated preference icon.
+ if (sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false)) {
+ consider3gAntiquatedPreference.setIcon(R.drawable.antiquated_3g_enabled)
+ } else {
+ consider3gAntiquatedPreference.setIcon(R.drawable.antiquated_3g_disabled)
+ }
// Set the bottom app bar preference icon.
if (sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)) {
- // Set the enabled icon.
bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_enabled)
} else {
- // Set the disabled icon.
bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_disabled)
}
- }
- // The listener should be unregistered when the app is paused.
- override fun onPause() {
- // Run the default commands.
- super.onPause()
-
- // Get a handle for the shared preferences.
- val sharedPreferences = preferenceScreen.sharedPreferences
-
- // Unregister the shared preference listener.
- sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
- }
+ // Check if the fragment has been restarted.
+ if (savedInstanceState != null) {
+ // Set the fragment restarted flag.
+ fragmentRestarted = true
- // The listener should be re-registered when the app is resumed.
- override fun onResume() {
- // Run the default commands.
- super.onResume()
-
- // Get a new shared preference change listener.
- sharedPreferenceChangeListener = getSharedPreferenceChangeListener()
-
- // Get a handle for the shared preferences.
- val sharedPreferences = preferenceScreen.sharedPreferences
-
- // Re-register the shared preference listener.
- sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
+ // Save the scroll Y.
+ scrollY = savedInstanceState.getInt(SCROLL_Y)
+ }
}
- private fun getSharedPreferenceChangeListener(): SharedPreferences.OnSharedPreferenceChangeListener {
+ private fun getSharedPreferenceChangeListener(): OnSharedPreferenceChangeListener {
// Return the shared preference change listener.
- return SharedPreferences.OnSharedPreferenceChangeListener {sharedPreferences, key ->
+ return OnSharedPreferenceChangeListener { sharedPreferences: SharedPreferences, key: String? ->
when (key) {
getString(R.string.realtime_monitoring_key) -> {
// Update the icon.
if (sharedPreferences.getBoolean(getString(R.string.realtime_monitoring_key), false)) {
- // Set the enabled icon.
- realtimeMonitoringPreference.setIcon(R.drawable.realtime_monitoring_enabled)
+ // Set the icons.
+ realtimeMonitoringPreference.setIcon(R.drawable.secure_notification_enabled)
+ secureNetworkNotificationPreference.setIcon(R.drawable.secure_notification_enabled)
+ insecureNetworkNotificationPreference.setIcon(R.drawable.insecure_notification_enabled)
+ antiquatedNetworkNotificationPreference.setIcon(R.drawable.antiquated_notification_enabled)
} else {
- // Set the disabled icon.
- realtimeMonitoringPreference.setIcon(R.drawable.realtime_monitoring_disabled)
+ // Set the icons.
+ realtimeMonitoringPreference.setIcon(R.drawable.secure_notification_disabled)
+ secureNetworkNotificationPreference.setIcon(R.drawable.secure_notification_disabled)
+ insecureNetworkNotificationPreference.setIcon(R.drawable.insecure_notification_disabled)
+ antiquatedNetworkNotificationPreference.setIcon(R.drawable.antiquated_notification_disabled)
}
// Start or stop the service.
if (sharedPreferences.getBoolean(getString(R.string.realtime_monitoring_key), false)) { // Realtime monitoring has been enabled.
- // Start the realtime monitoring service.
- requireActivity().startService(Intent(context, RealtimeMonitoringService::class.java))
+ // Start the service according to the API.
+ if (Build.VERSION.SDK_INT >= 33) { // The device API is >= 33.
+ // Check to see if the post notification has been granted.
+ if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) { // The permission has been granted.
+ // Start the realtime monitoring service.
+ requireActivity().startService(Intent(context, RealtimeMonitoringService::class.java))
+ } else { // The post notification permission has not been granted.
+ // Check if the user has previously denied the post notifications permission.
+ if (ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), Manifest.permission.POST_NOTIFICATIONS)) { // Show a dialog explaining the request first.
+ // Check to see if a notification permission dialog is already displayed. This happens if the app is restarted while the dialog is shown.
+ if (requireActivity().supportFragmentManager.findFragmentByTag(getString(R.string.notification_permission)) == null) { // No dialog is currently shown.
+ // Instantiate the notification permission dialog fragment.
+ val notificationPermissionDialogFragment = NotificationPermissionDialog()
+
+ // Show the notification permission alert dialog. The permission will be requested when the dialog is closed.
+ notificationPermissionDialogFragment.show(requireActivity().supportFragmentManager, getString(R.string.notification_permission))
+ }
+ } else { // Show the permission request directly.
+ // Request the post notifications permission directly.
+ ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.POST_NOTIFICATIONS), PrivacyCellActivity.NOTIFICATION_PERMISSION_REQUEST_CODE)
+ }
+ }
+ } else { // The device API is < 33.
+ // Start the realtime monitoring service.
+ requireActivity().startService(Intent(context, RealtimeMonitoringService::class.java))
+ }
} else { // Realtime monitoring has been disabled.
// Stop the realtime monitoring service.
requireActivity().stopService(Intent(context, RealtimeMonitoringService::class.java))
}
}
+ getString(R.string.consider_3g_antiquated_key) -> {
+ // Update the icon.
+ if (sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false)) {
+ consider3gAntiquatedPreference.setIcon(R.drawable.antiquated_3g_enabled)
+ } else {
+ consider3gAntiquatedPreference.setIcon(R.drawable.antiquated_3g_disabled)
+ }
+
+ // Restart Privacy Cell.
+ restartPrivacyCell()
+ }
+
getString(R.string.bottom_app_bar_key) -> {
// Update the icon.
if (sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)) {
- // Set the enabled icon.
bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_enabled)
} else {
- // Set the disabled icon.
bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_disabled)
}
- // Create an intent to restart Privacy Cell.
- val restartIntent = requireActivity().parentActivityIntent!!
+ // Restart Privacy Cell after 400 milliseconds.
+ restartPrivacyCell()
+ }
+ }
+ }
+ }
+
+ // The listener should be unregistered when the app is paused.
+ override fun onPause() {
+ // Run the default commands.
+ super.onPause()
- // `Intent.FLAG_ACTIVITY_CLEAR_TASK` removes all activities from the stack. It requires `Intent.FLAG_ACTIVITY_NEW_TASK`.
- restartIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ // Get a handle for the shared preferences.
+ val sharedPreferences = preferenceScreen.sharedPreferences!!
- // Create a handler to restart the activity.
- val restartHandler = Handler(Looper.getMainLooper())
+ // Unregister the shared preference listener.
+ sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
+ }
- // Create a runnable to restart the activity.
- val restartRunnable = Runnable {
- // Restart the activity.
- startActivity(restartIntent)
- }
+ // The listener should be re-registered when the app is resumed.
+ override fun onResume() {
+ // Run the default commands.
+ super.onResume()
- // Restart the activity after 400 milliseconds, so that the app has enough time to save the change to the preference.
- restartHandler.postDelayed(restartRunnable, 400)
- }
- }
+ // Get a new shared preference change listener.
+ sharedPreferenceChangeListener = getSharedPreferenceChangeListener()
+
+ // Get a handle for the shared preferences.
+ val sharedPreferences = preferenceScreen.sharedPreferences!!
+
+ // Re-register the shared preference listener.
+ sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
+
+ // Update the realtime monitoring preference summary.
+ updateRealtimeMonitoringSummary()
+
+ // Restore the scroll position if the fragment has been restarted.
+ if (fragmentRestarted) {
+ // Reset the fragment restarted flag.
+ fragmentRestarted = false
+
+ // Set the scroll position.
+ listView.smoothScrollBy(0, scrollY)
+ }
+ }
+
+ override fun onSaveInstanceState(savedInstanceState: Bundle) {
+ // Run the default commands.
+ super.onSaveInstanceState(savedInstanceState)
+
+ // Save the scroll position.
+ savedInstanceState.putInt(SCROLL_Y, listView.computeVerticalScrollOffset())
+ }
+
+ private fun restartPrivacyCell() {
+ // Create an intent to restart Privacy Cell.
+ val restartIntent = requireActivity().parentActivityIntent!!
+
+ // `Intent.FLAG_ACTIVITY_CLEAR_TASK` removes all activities from the stack. It requires `Intent.FLAG_ACTIVITY_NEW_TASK`.
+ restartIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+
+ // Create a handler to restart the activity.
+ val restartHandler = Handler(Looper.getMainLooper())
+
+ // Create a runnable to restart the activity.
+ val restartRunnable = Runnable {
+ // Restart the activity.
+ startActivity(restartIntent)
+ }
+
+ // Restart the activity after 400 milliseconds, so that the app has enough time to save the change to the preference.
+ restartHandler.postDelayed(restartRunnable, 400)
+ }
+
+ fun updateRealtimeMonitoringSummary() {
+ // Update the summary according to the API.
+ if (Build.VERSION.SDK_INT >= 33) { // The API >= 33.
+ // Set the summary according to the notification permission status.
+ if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED)
+ realtimeMonitoringPreference.summary = getString(R.string.realtime_monitoring_summary)
+ else
+ realtimeMonitoringPreference.summary = (getString(R.string.realtime_monitoring_summary) + " " + getString(R.string.notification_permission_denied))
+ } else { // The API is < 33.
+ // Set the realtime monitoring summary.
+ realtimeMonitoringPreference.summary = getString(R.string.realtime_monitoring_summary)
}
}
-}
\ No newline at end of file
+}