/* * Copyright 2021-2023 Soren Stoutner . * * This file is part of Privacy Cell . * * Privacy Cell is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Privacy Cell is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Privacy Cell. If not, see . */ package com.stoutner.privacycell.fragments 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 android.provider.Settings import androidx.core.app.ActivityCompat import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat 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: 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!! // Get handles for the preferences. realtimeMonitoringPreference = findPreference(getString(R.string.realtime_monitoring_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. realtimeMonitoringPreference.isEnabled = (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) // Set the realtime monitoring icon according to the status. if (realtimeMonitoringPreference.isEnabled) { // Set the realtime monitoring preference icon. if (sharedPreferences.getBoolean(getString(R.string.realtime_monitoring_key), false)) { // 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 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 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) .putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName) .putExtra(Settings.EXTRA_CHANNEL_ID, RealtimeMonitoringService.SECURE_NETWORK) 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)) { bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_enabled) } else { bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_disabled) } // Check if the fragment has been restarted. if (savedInstanceState != null) { // Set the fragment restarted flag. fragmentRestarted = true // Save the scroll Y. scrollY = savedInstanceState.getInt(SCROLL_Y) } } private fun getSharedPreferenceChangeListener(): OnSharedPreferenceChangeListener { // Return the shared preference change listener. 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 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 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 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)) // Cancel the realtime listener work request. WorkManager.getInstance(requireContext()).cancelUniqueWork(getString(R.string.register_listener_work_request)) } } 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)) { bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_enabled) } else { bottomAppBarPreference.setIcon(R.drawable.bottom_app_bar_disabled) } // 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() // Get a handle for the shared preferences. val sharedPreferences = preferenceScreen.sharedPreferences!! // Unregister the shared preference listener. sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener) } // 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) // 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) } } }