2 * Copyright 2021-2022 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Cell <https://www.stoutner.com/privacy-cell>.
6 * Privacy Cell is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Cell is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Cell. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacycell.fragments
22 import android.Manifest
23 import android.content.Intent
24 import android.content.SharedPreferences
25 import android.content.SharedPreferences.OnSharedPreferenceChangeListener
26 import android.content.pm.PackageManager
27 import android.os.Build
28 import android.os.Bundle
29 import android.os.Handler
30 import android.os.Looper
31 import android.provider.Settings
33 import androidx.core.app.ActivityCompat
34 import androidx.preference.Preference
35 import androidx.preference.PreferenceFragmentCompat
36 import androidx.work.WorkManager
38 import com.stoutner.privacycell.R
39 import com.stoutner.privacycell.activities.PrivacyCellActivity
40 import com.stoutner.privacycell.dialogs.NotificationPermissionDialog
41 import com.stoutner.privacycell.services.RealtimeMonitoringService
43 // Define the class constants.
44 private const val SCROLL_Y = "scroll_y"
46 class SettingsFragment : PreferenceFragmentCompat() {
47 // Declare the class variables.
48 private lateinit var sharedPreferenceChangeListener: OnSharedPreferenceChangeListener
50 // Declare the class views.
51 private lateinit var realtimeMonitoringPreference: Preference
52 private lateinit var secureNetworkNotificationPreference: Preference
53 private lateinit var insecureNetworkNotificationPreference: Preference
54 private lateinit var antiquatedNetworkNotificationPreference: Preference
55 private lateinit var consider3gAntiquatedPreference: Preference
56 private lateinit var bottomAppBarPreference: Preference
59 // Declare the private static class variables. Otherwise, onRestart will not pull the same values that are populated from the saved instance state.
60 private var fragmentRestarted: Boolean = false
61 private var scrollY: Int = 0
64 override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
65 // Load the preferences from the XML file.
66 setPreferencesFromResource(R.xml.preferences, rootKey)
68 // Get a handle for the shared preferences.
69 val sharedPreferences = preferenceScreen.sharedPreferences!!
71 // Get handles for the preferences.
72 realtimeMonitoringPreference = findPreference(getString(R.string.realtime_monitoring_key))!!
73 secureNetworkNotificationPreference = findPreference(getString(R.string.secure_network_notification_key))!!
74 insecureNetworkNotificationPreference = findPreference(getString(R.string.insecure_network_notification_key))!!
75 antiquatedNetworkNotificationPreference = findPreference(getString(R.string.antiquated_network_notification_key))!!
76 consider3gAntiquatedPreference = findPreference(getString(R.string.consider_3g_antiquated_key))!!
77 bottomAppBarPreference = findPreference(getString(R.string.bottom_app_bar_key))!!
79 // Only enable the realtime monitoring preference if the READ_PHONE_STATE permission has been granted.
80 realtimeMonitoringPreference.isEnabled = (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED)
82 // Set the realtime monitoring icon according to the status.
83 if (realtimeMonitoringPreference.isEnabled) {
84 // Set the realtime monitoring preference icon.
85 if (sharedPreferences.getBoolean(getString(R.string.realtime_monitoring_key), false)) {
87 realtimeMonitoringPreference.setIcon(R.drawable.secure_notification_enabled)
88 secureNetworkNotificationPreference.setIcon(R.drawable.secure_notification_enabled)
89 insecureNetworkNotificationPreference.setIcon(R.drawable.insecure_notification_enabled)
90 antiquatedNetworkNotificationPreference.setIcon(R.drawable.antiquated_notification_enabled)
93 realtimeMonitoringPreference.setIcon(R.drawable.secure_notification_disabled)
94 secureNetworkNotificationPreference.setIcon(R.drawable.secure_notification_disabled)
95 insecureNetworkNotificationPreference.setIcon(R.drawable.insecure_notification_disabled)
96 antiquatedNetworkNotificationPreference.setIcon(R.drawable.antiquated_notification_disabled)
100 realtimeMonitoringPreference.setIcon(R.drawable.secure_notification_ghosted)
101 secureNetworkNotificationPreference.setIcon(R.drawable.secure_notification_ghosted)
102 insecureNetworkNotificationPreference.setIcon(R.drawable.insecure_notification_ghosted)
103 antiquatedNetworkNotificationPreference.setIcon(R.drawable.antiquated_notification_ghosted)
106 // Set the notification preferences to depend on the realtime monitoring preference.
107 secureNetworkNotificationPreference.dependency = getString(R.string.realtime_monitoring_key)
108 insecureNetworkNotificationPreference.dependency = getString(R.string.realtime_monitoring_key)
109 antiquatedNetworkNotificationPreference.dependency = getString(R.string.realtime_monitoring_key)
111 // Create the notification intents.
112 val secureNetworkNotificationIntent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
113 .putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
114 .putExtra(Settings.EXTRA_CHANNEL_ID, RealtimeMonitoringService.SECURE_NETWORK)
115 val insecureNetworkNotificationIntent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
116 .putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
117 .putExtra(Settings.EXTRA_CHANNEL_ID, RealtimeMonitoringService.INSECURE_NETWORK)
118 val antiquatedNetworkNotificationIntent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
119 .putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
120 .putExtra(Settings.EXTRA_CHANNEL_ID, RealtimeMonitoringService.ANTIQUATED_NETWORK)
122 // Set the notification preference intents.
123 secureNetworkNotificationPreference.intent = secureNetworkNotificationIntent
124 insecureNetworkNotificationPreference.intent = insecureNetworkNotificationIntent
125 antiquatedNetworkNotificationPreference.intent = antiquatedNetworkNotificationIntent
127 // Set the consider 3G antiquated preference icon.
128 if (sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false)) {
129 consider3gAntiquatedPreference.setIcon(R.drawable.antiquated_3g_enabled)
131 consider3gAntiquatedPreference.setIcon(R.drawable.antiquated_3g_disabled)
134 // Set the bottom app bar preference icon.
135 if (sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)) {
136 bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_enabled)
138 bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_disabled)
141 // Check if the fragment has been restarted.
142 if (savedInstanceState != null) {
143 // Set the fragment restored flag.
144 fragmentRestarted = true
146 // Save the scroll Y.
147 scrollY = savedInstanceState.getInt(SCROLL_Y)
151 private fun getSharedPreferenceChangeListener(): OnSharedPreferenceChangeListener {
152 // Return the shared preference change listener.
153 return OnSharedPreferenceChangeListener { sharedPreferences: SharedPreferences, key: String? ->
155 getString(R.string.realtime_monitoring_key) -> {
157 if (sharedPreferences.getBoolean(getString(R.string.realtime_monitoring_key), false)) {
159 realtimeMonitoringPreference.setIcon(R.drawable.secure_notification_enabled)
160 secureNetworkNotificationPreference.setIcon(R.drawable.secure_notification_enabled)
161 insecureNetworkNotificationPreference.setIcon(R.drawable.insecure_notification_enabled)
162 antiquatedNetworkNotificationPreference.setIcon(R.drawable.antiquated_notification_enabled)
165 realtimeMonitoringPreference.setIcon(R.drawable.secure_notification_disabled)
166 secureNetworkNotificationPreference.setIcon(R.drawable.secure_notification_disabled)
167 insecureNetworkNotificationPreference.setIcon(R.drawable.insecure_notification_disabled)
168 antiquatedNetworkNotificationPreference.setIcon(R.drawable.antiquated_notification_disabled)
171 // Start or stop the service.
172 if (sharedPreferences.getBoolean(getString(R.string.realtime_monitoring_key), false)) { // Realtime monitoring has been enabled.
173 // Start the service according to the API.
174 if (Build.VERSION.SDK_INT >= 33) { // The device API is >= 33.
175 // Check to see if the post notification has been granted.
176 if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) { // The permission has been granted.
177 // Start the realtime monitoring service.
178 requireActivity().startService(Intent(context, RealtimeMonitoringService::class.java))
179 } else { // The post notification permission has not been granted.
180 // Check if the user has previously denied the post notifications permission.
181 if (ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), Manifest.permission.POST_NOTIFICATIONS)) { // Show a dialog explaining the request first.
182 // Check to see if a notification permission dialog is already displayed. This happens if the app is restarted while the dialog is shown.
183 if (requireActivity().supportFragmentManager.findFragmentByTag(getString(R.string.notification_permission)) == null) { // No dialog is currently shown.
184 // Instantiate the notification permission dialog fragment.
185 val notificationPermissionDialogFragment = NotificationPermissionDialog()
187 // Show the notification permission alert dialog. The permission will be requested when the dialog is closed.
188 notificationPermissionDialogFragment.show(requireActivity().supportFragmentManager, getString(R.string.notification_permission))
190 } else { // Show the permission request directly.
191 // Request the post notifications permission directly.
192 ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.POST_NOTIFICATIONS), PrivacyCellActivity.NOTIFICATION_PERMISSION_REQUEST_CODE)
195 } else { // The device API is < 33.
196 // Start the realtime monitoring service.
197 requireActivity().startService(Intent(context, RealtimeMonitoringService::class.java))
199 } else { // Realtime monitoring has been disabled.
200 // Stop the realtime monitoring service.
201 requireActivity().stopService(Intent(context, RealtimeMonitoringService::class.java))
203 // Cancel the realtime listener work request.
204 WorkManager.getInstance(requireContext()).cancelUniqueWork(getString(R.string.register_listener_work_request))
208 getString(R.string.consider_3g_antiquated_key) -> {
210 if (sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false)) {
211 consider3gAntiquatedPreference.setIcon(R.drawable.antiquated_3g_enabled)
213 consider3gAntiquatedPreference.setIcon(R.drawable.antiquated_3g_disabled)
216 // Restart Privacy Cell.
220 getString(R.string.bottom_app_bar_key) -> {
222 if (sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)) {
223 bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_enabled)
225 bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_disabled)
228 // Restart Privacy Cell after 400 milliseconds.
235 // The listener should be unregistered when the app is paused.
236 override fun onPause() {
237 // Run the default commands.
240 // Get a handle for the shared preferences.
241 val sharedPreferences = preferenceScreen.sharedPreferences!!
243 // Unregister the shared preference listener.
244 sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
247 // The listener should be re-registered when the app is resumed.
248 override fun onResume() {
249 // Run the default commands.
252 // Get a new shared preference change listener.
253 sharedPreferenceChangeListener = getSharedPreferenceChangeListener()
255 // Get a handle for the shared preferences.
256 val sharedPreferences = preferenceScreen.sharedPreferences!!
258 // Re-register the shared preference listener.
259 sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
261 // Update the realtime monitoring preference summary.
262 updateRealtimeMonitoringSummary()
264 // Restore the scroll position if the fragment has been restarted.
265 if (fragmentRestarted) {
266 // Reset the fragment restarted flag.
267 fragmentRestarted = false
269 // Set the scroll position.
270 listView.smoothScrollBy(0, scrollY)
274 override fun onSaveInstanceState(savedInstanceState: Bundle) {
275 // Run the default commands.
276 super.onSaveInstanceState(savedInstanceState)
278 // Save the scroll position.
279 savedInstanceState.putInt(SCROLL_Y, listView.computeVerticalScrollOffset())
282 private fun restartPrivacyCell() {
283 // Create an intent to restart Privacy Cell.
284 val restartIntent = requireActivity().parentActivityIntent!!
286 // `Intent.FLAG_ACTIVITY_CLEAR_TASK` removes all activities from the stack. It requires `Intent.FLAG_ACTIVITY_NEW_TASK`.
287 restartIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
289 // Create a handler to restart the activity.
290 val restartHandler = Handler(Looper.getMainLooper())
292 // Create a runnable to restart the activity.
293 val restartRunnable = Runnable {
294 // Restart the activity.
295 startActivity(restartIntent)
298 // Restart the activity after 400 milliseconds, so that the app has enough time to save the change to the preference.
299 restartHandler.postDelayed(restartRunnable, 400)
302 fun updateRealtimeMonitoringSummary() {
303 // Update the summary according to the API.
304 if (Build.VERSION.SDK_INT >= 33) { // The API >= 33.
305 // Set the summary according to the notification permission status.
306 if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED)
307 realtimeMonitoringPreference.summary = getString(R.string.realtime_monitoring_summary)
309 realtimeMonitoringPreference.summary = (getString(R.string.realtime_monitoring_summary) + " " + getString(R.string.notification_permission_denied))
310 } else { // The API is < 33.
311 // Set the realtime monitoring summary.
312 realtimeMonitoringPreference.summary = getString(R.string.realtime_monitoring_summary)