]> gitweb.stoutner.com Git - PrivacyCell.git/blob - app/src/main/java/com/stoutner/privacycell/fragments/SettingsFragment.kt
Restore the scroll position when restarting the Settings activity. https://redmine...
[PrivacyCell.git] / app / src / main / java / com / stoutner / privacycell / fragments / SettingsFragment.kt
1 /*
2  * Copyright 2021-2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Cell <https://www.stoutner.com/privacy-cell>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacycell.fragments
21
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
32
33 import androidx.core.app.ActivityCompat
34 import androidx.preference.Preference
35 import androidx.preference.PreferenceFragmentCompat
36 import androidx.work.WorkManager
37
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
42
43 // Define the class constants.
44 private const val SCROLL_Y = "scroll_y"
45
46 class SettingsFragment : PreferenceFragmentCompat() {
47     // Declare the class variables.
48     private lateinit var sharedPreferenceChangeListener: OnSharedPreferenceChangeListener
49
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
57
58     companion object {
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
62     }
63
64     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
65         // Load the preferences from the XML file.
66         setPreferencesFromResource(R.xml.preferences, rootKey)
67
68         // Get a handle for the shared preferences.
69         val sharedPreferences = preferenceScreen.sharedPreferences!!
70
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))!!
78
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)
81
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)) {
86                 // Set the icons.
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)
91             } else {
92                 // Set the icons.
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)
97             }
98         } else {
99             // Set the icons.
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)
104         }
105
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)
110
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)
121
122         // Set the notification preference intents.
123         secureNetworkNotificationPreference.intent = secureNetworkNotificationIntent
124         insecureNetworkNotificationPreference.intent = insecureNetworkNotificationIntent
125         antiquatedNetworkNotificationPreference.intent = antiquatedNetworkNotificationIntent
126
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)
130         } else {
131             consider3gAntiquatedPreference.setIcon(R.drawable.antiquated_3g_disabled)
132         }
133
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)
137         } else {
138             bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_disabled)
139         }
140
141         // Check if the fragment has been restarted.
142         if (savedInstanceState != null) {
143             // Set the fragment restored flag.
144             fragmentRestarted = true
145
146             // Save the scroll Y.
147             scrollY = savedInstanceState.getInt(SCROLL_Y)
148         }
149     }
150
151     private fun getSharedPreferenceChangeListener(): OnSharedPreferenceChangeListener {
152         // Return the shared preference change listener.
153         return OnSharedPreferenceChangeListener { sharedPreferences: SharedPreferences, key: String? ->
154             when (key) {
155                 getString(R.string.realtime_monitoring_key) -> {
156                     // Update the icon.
157                     if (sharedPreferences.getBoolean(getString(R.string.realtime_monitoring_key), false)) {
158                         // Set the icons.
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)
163                     } else {
164                         // Set the icons.
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)
169                     }
170
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()
186
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))
189                                     }
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)
193                                 }
194                             }
195                         } else {   // The device API is < 33.
196                             // Start the realtime monitoring service.
197                             requireActivity().startService(Intent(context, RealtimeMonitoringService::class.java))
198                         }
199                     } else {  // Realtime monitoring has been disabled.
200                         // Stop the realtime monitoring service.
201                         requireActivity().stopService(Intent(context, RealtimeMonitoringService::class.java))
202
203                         // Cancel the realtime listener work request.
204                         WorkManager.getInstance(requireContext()).cancelUniqueWork(getString(R.string.register_listener_work_request))
205                     }
206                 }
207
208                 getString(R.string.consider_3g_antiquated_key) -> {
209                     // Update the icon.
210                     if (sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false)) {
211                         consider3gAntiquatedPreference.setIcon(R.drawable.antiquated_3g_enabled)
212                     } else {
213                         consider3gAntiquatedPreference.setIcon(R.drawable.antiquated_3g_disabled)
214                     }
215
216                     // Restart Privacy Cell.
217                     restartPrivacyCell()
218                 }
219
220                 getString(R.string.bottom_app_bar_key) -> {
221                     // Update the icon.
222                     if (sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)) {
223                         bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_enabled)
224                     } else {
225                         bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_disabled)
226                     }
227
228                     // Restart Privacy Cell after 400 milliseconds.
229                     restartPrivacyCell()
230                 }
231             }
232         }
233     }
234
235     // The listener should be unregistered when the app is paused.
236     override fun onPause() {
237         // Run the default commands.
238         super.onPause()
239
240         // Get a handle for the shared preferences.
241         val sharedPreferences = preferenceScreen.sharedPreferences!!
242
243         // Unregister the shared preference listener.
244         sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
245     }
246
247     // The listener should be re-registered when the app is resumed.
248     override fun onResume() {
249         // Run the default commands.
250         super.onResume()
251
252         // Get a new shared preference change listener.
253         sharedPreferenceChangeListener = getSharedPreferenceChangeListener()
254
255         // Get a handle for the shared preferences.
256         val sharedPreferences = preferenceScreen.sharedPreferences!!
257
258         // Re-register the shared preference listener.
259         sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
260
261         // Update the realtime monitoring preference summary.
262         updateRealtimeMonitoringSummary()
263
264         // Restore the scroll position if the fragment has been restarted.
265         if (fragmentRestarted) {
266             // Reset the fragment restarted flag.
267             fragmentRestarted = false
268
269             // Set the scroll position.
270             listView.smoothScrollBy(0, scrollY)
271         }
272     }
273
274     override fun onSaveInstanceState(savedInstanceState: Bundle) {
275         // Run the default commands.
276         super.onSaveInstanceState(savedInstanceState)
277
278         // Save the scroll position.
279         savedInstanceState.putInt(SCROLL_Y, listView.computeVerticalScrollOffset())
280     }
281
282     private fun restartPrivacyCell() {
283         // Create an intent to restart Privacy Cell.
284         val restartIntent = requireActivity().parentActivityIntent!!
285
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
288
289         // Create a handler to restart the activity.
290         val restartHandler = Handler(Looper.getMainLooper())
291
292         // Create a runnable to restart the activity.
293         val restartRunnable = Runnable {
294             // Restart the activity.
295             startActivity(restartIntent)
296         }
297
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)
300     }
301
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)
308             else
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)
313         }
314     }
315 }