X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyCell.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacycell%2Fservices%2FRealtimeMonitoringService.kt;h=c54e290813c07abd015b1f3d0443dc7afdc45317;hp=7d467dd1d2576165407af96325896c9d6cf0d90d;hb=HEAD;hpb=9ee51bf9fbf3ef3e463456c4a4e4ef626eab3412 diff --git a/app/src/main/java/com/stoutner/privacycell/services/RealtimeMonitoringService.kt b/app/src/main/java/com/stoutner/privacycell/services/RealtimeMonitoringService.kt index 7d467dd..72ace81 100644 --- a/app/src/main/java/com/stoutner/privacycell/services/RealtimeMonitoringService.kt +++ b/app/src/main/java/com/stoutner/privacycell/services/RealtimeMonitoringService.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Soren Stoutner . + * Copyright 2021-2023 Soren Stoutner . * * This file is part of Privacy Cell . * @@ -17,6 +17,9 @@ * along with Privacy Cell. If not, see . */ +// The suppression of deprecation lint can be removed once the minimum API >= 31. +@file:Suppress("DEPRECATION") + package com.stoutner.privacycell.services import android.Manifest @@ -32,16 +35,19 @@ import android.content.pm.PackageManager import android.os.Binder import android.os.IBinder import android.telephony.PhoneStateListener // This can be replaced by `TelephonyCallback` once the minimum API >= 31. +import android.telephony.ServiceState import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyManager import androidx.core.app.ActivityCompat +import androidx.preference.PreferenceManager import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager import com.stoutner.privacycell.R import com.stoutner.privacycell.activities.PrivacyCellActivity +import com.stoutner.privacycell.helpers.ProtocolHelper import com.stoutner.privacycell.workers.RegisterRealtimeListenerWorker import java.util.concurrent.TimeUnit @@ -56,11 +62,18 @@ class RealtimeMonitoringService : Service() { // Define the public constants. These are used in the settings fragment to launch intents to edit the sound that plays for each channel. const val SECURE_NETWORK = "secure_network" const val INSECURE_NETWORK = "insecure_network" + const val ANTIQUATED_NETWORK = "antiquated_network" } // Define the class variables. private var currentStatus = "" + private var voiceNetworkSecurityStatus = ProtocolHelper.UNPOPULATED + private var dataNetworkSecurityStatus = ProtocolHelper.UNPOPULATED + + // Declare the class variables. + private lateinit var notificationManager: NotificationManager private lateinit var phoneStateListener: PhoneStateListener // The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31. + private lateinit var privacyCellPendingIntent: PendingIntent inner class ServiceBinder : Binder() { // Get a copy of this service as a binder. @@ -73,124 +86,134 @@ class RealtimeMonitoringService : Service() { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - // Get a handle for the notification manager. - val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - // Create a notification channel group. - notificationManager.createNotificationChannelGroup(NotificationChannelGroup(REALTIME_MONITORING, getString(R.string.realtime_monitoring))) + // Get a handle for the shared preferences. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) - // Prepare the notification channels. - val secureNetworkChannel = NotificationChannel(SECURE_NETWORK, getString(R.string.secure_network_channel), NotificationManager.IMPORTANCE_HIGH) - val insecureNetworkChannel = NotificationChannel(INSECURE_NETWORK, getString(R.string.insecure_network_channel), NotificationManager.IMPORTANCE_HIGH) - val unknownNetworkChannel = NotificationChannel(UNKNOWN_NETWORK, getString(R.string.unknown_network_channel), NotificationManager.IMPORTANCE_LOW) + // Check to see if realtime monitoring is enabled. Sometimes the shared preferences can't return a value in time, because Android sucks. + // So, the default value is set to true, which is the safest value if the shared preferences can't be queried. + if (sharedPreferences.getBoolean(applicationContext.getString(R.string.realtime_monitoring_key), true)) { // Realtime monitoring is enabled. + // Get a handle for the notification manager. + notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - // Set the notification channel group. - secureNetworkChannel.group = REALTIME_MONITORING - insecureNetworkChannel.group = REALTIME_MONITORING - unknownNetworkChannel.group = REALTIME_MONITORING + // Create a notification channel group. + notificationManager.createNotificationChannelGroup(NotificationChannelGroup(REALTIME_MONITORING, getString(R.string.realtime_monitoring))) - // Disable the notification dots. - secureNetworkChannel.setShowBadge(false) - insecureNetworkChannel.setShowBadge(false) - unknownNetworkChannel.setShowBadge(false) + // Prepare the notification channels. + val secureNetworkChannel = NotificationChannel(SECURE_NETWORK, getString(R.string.secure_network_channel), NotificationManager.IMPORTANCE_HIGH) + val insecureNetworkChannel = NotificationChannel(INSECURE_NETWORK, getString(R.string.insecure_network_channel), NotificationManager.IMPORTANCE_HIGH) + val antiquatedNetworkChannel = NotificationChannel(ANTIQUATED_NETWORK, getString(R.string.antiquated_network_channel), NotificationManager.IMPORTANCE_HIGH) + val unknownNetworkChannel = NotificationChannel(UNKNOWN_NETWORK, getString(R.string.unknown_network_channel), NotificationManager.IMPORTANCE_LOW) - // Set the notifications to be public for the secure and insecure networks. - secureNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC - insecureNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC + // Set the notification channel group. + secureNetworkChannel.group = REALTIME_MONITORING + insecureNetworkChannel.group = REALTIME_MONITORING + antiquatedNetworkChannel.group = REALTIME_MONITORING + unknownNetworkChannel.group = REALTIME_MONITORING - // Create the notification channels. - notificationManager.createNotificationChannel(secureNetworkChannel) - notificationManager.createNotificationChannel(insecureNetworkChannel) - notificationManager.createNotificationChannel(unknownNetworkChannel) + // Disable the notification dots. + secureNetworkChannel.setShowBadge(false) + insecureNetworkChannel.setShowBadge(false) + antiquatedNetworkChannel.setShowBadge(false) + unknownNetworkChannel.setShowBadge(false) - // Create a notification builder. - val notificationBuilder = Notification.Builder(this, UNKNOWN_NETWORK) + // Set the primary channel notifications to be public. + secureNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC + insecureNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC + antiquatedNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC - // Create an intent to open Privacy Cell. - val privacyCellIntent = Intent(this, PrivacyCellActivity::class.java) + // Create the notification channels. + notificationManager.createNotificationChannel(secureNetworkChannel) + notificationManager.createNotificationChannel(insecureNetworkChannel) + notificationManager.createNotificationChannel(antiquatedNetworkChannel) + notificationManager.createNotificationChannel(unknownNetworkChannel) - // Create a pending intent from the Privacy Cell intent. - val privacyCellPendingIntent = PendingIntent.getActivity(this, 0, privacyCellIntent, PendingIntent.FLAG_IMMUTABLE) + // Create a notification builder. + val notificationBuilder = Notification.Builder(this, UNKNOWN_NETWORK) - // Set the notification to open Privacy Cell. - notificationBuilder.setContentIntent(privacyCellPendingIntent) + // Create an intent to open Privacy Cell. + val privacyCellIntent = Intent(this, PrivacyCellActivity::class.java) - // Set the notification text. - notificationBuilder.setContentText(getString(R.string.unknown_network)) + // Create a pending intent from the Privacy Cell intent. + privacyCellPendingIntent = PendingIntent.getActivity(this, 0, privacyCellIntent, PendingIntent.FLAG_IMMUTABLE) - // Set the notification icon. - notificationBuilder.setSmallIcon(R.drawable.insecure_notification) + // Set the notification to open Privacy Cell. + notificationBuilder.setContentIntent(privacyCellPendingIntent) - // Set the color. - notificationBuilder.setColor(getColor(R.color.red_text)) + // Set the notification text. + notificationBuilder.setContentText(getString(R.string.unknown_network)) - // Start the foreground notification. - startForeground(NOTIFICATION_ID, notificationBuilder.build()) + // Set the notification icon. + notificationBuilder.setSmallIcon(R.drawable.antiquated_notification_enabled) - // Define the phone state listener. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31. - phoneStateListener = object : PhoneStateListener() { - override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) { - // Populate the notification according to the network type. - if (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_NR) { // This is a secure 5G NR SA network. - // Only update the notification if the network status has changed. - if (currentStatus != SECURE_NETWORK) { - // Create a secure network notification builder. - val secureNetworkNotificationBuilder = Notification.Builder(applicationContext, SECURE_NETWORK) + // Set the color. + notificationBuilder.setColor(getColor(R.color.red_notification_icon)) - // Set the notification to open Privacy Cell. - secureNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent) + // Prevent swiping to dismiss the notification. This no longer works (except on the lock screen) in API >= 34. + notificationBuilder.setOngoing(true) - // Set the notification text. - secureNetworkNotificationBuilder.setContentText(getString(R.string.secure_network)) + // Start the foreground notification. + startForeground(NOTIFICATION_ID, notificationBuilder.build()) - // Set the notification icon. - secureNetworkNotificationBuilder.setSmallIcon(R.drawable.secure_notification) + // Instantiate the protocol helper. + val protocolHelper = ProtocolHelper() - // Set the color. - secureNetworkNotificationBuilder.setColor(getColor(R.color.blue_text)) - - // Update the notification. - notificationManager.notify(NOTIFICATION_ID, secureNetworkNotificationBuilder.build()) + // Get a handle for the telephony manager. + val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager - // Store the new network status. - currentStatus = SECURE_NETWORK + // Define the phone state listener. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31. + phoneStateListener = object : PhoneStateListener() { + @Deprecated("Deprecated in Java") + override fun onServiceStateChanged(serviceState: ServiceState) { // Update the voice network status. + // Check to see if realtime monitoring is enabled. Sometimes the system keeps running the service even when it is supposed to shut down. + if (sharedPreferences.getBoolean(applicationContext.getString(R.string.realtime_monitoring_key), true)) { // Realtime monitoring is enabled. + // Get the network registration info for the voice network, which is the second of the three entries (the first appears to be Wi-Fi and the third appears to be the cell data network). + val networkRegistrationInfo = serviceState.networkRegistrationInfoList[1] + + // Get the consider 3G antiquated preference. + val consider3gAntiquated = sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false) + + // Update the voice network security status. + voiceNetworkSecurityStatus = protocolHelper.checkNetwork(networkRegistrationInfo.accessNetworkTechnology, consider3gAntiquated) + + // Populate the notification. + populateNotification() + } else { // Realtime monitoring is disabled. + // Cancel the current listener if it exists. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31. + telephonyManager.listen(phoneStateListener, LISTEN_NONE) } - } else { // This is not a secure 5G NR SA network. - // Only update the notification if the network status has changed. - if (currentStatus != INSECURE_NETWORK) { - // Create an insecure network notification builder. - val insecureNetworkNotificationBuilder = Notification.Builder(applicationContext, INSECURE_NETWORK) - - // Set the notification to open Privacy Cell. - insecureNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent) - - // Set the notification text. - insecureNetworkNotificationBuilder.setContentText(getString(R.string.insecure_network)) - - // Set the notification icon. - insecureNetworkNotificationBuilder.setSmallIcon(R.drawable.insecure_notification) - - // Set the color. - insecureNetworkNotificationBuilder.setColor(getColor(R.color.red_text)) - - // Update the notification. - notificationManager.notify(NOTIFICATION_ID, insecureNetworkNotificationBuilder.build()) + } - // Store the new network status. - currentStatus = INSECURE_NETWORK + @Deprecated("Deprecated in Java") + override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) { // Update the data network status. + // Check to see if realtime monitoring is enabled. Sometimes the system keeps running the service even when it is supposed to shut down. + if (sharedPreferences.getBoolean(applicationContext.getString(R.string.realtime_monitoring_key), true)) { // Realtime monitoring is enabled. + // Get the consider 3G antiquated preference. + val consider3gAntiquated = sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false) + + // Update the data network security status. + dataNetworkSecurityStatus = protocolHelper.checkNetwork(telephonyDisplayInfo.networkType, consider3gAntiquated) + + // Populate the notification. + populateNotification() + } else { // Realtime monitoring is disabled. + // Cancel the current listener if it exists. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31. + telephonyManager.listen(phoneStateListener, LISTEN_NONE) } } } - } - // Check to see if the read phone state permission has been granted. - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { - // Create a register realtime listener work request that fires every hour. - // This periodic request will fire shortly after being created (it fires about every hour near the beginning of the hour) and will reregister the listener if it gets garbage collected. - val registerRealtimeListenerWorkRequest = PeriodicWorkRequestBuilder(1, TimeUnit.HOURS).build() + // Check to see if the read phone state permission has been granted. + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { + // Create a register realtime listener work request that fires every fifteen minutes. + // This periodic request will fire shortly after being created and will reregister the listener if it gets garbage collected. + val registerRealtimeListenerWorkRequest = PeriodicWorkRequestBuilder(15, TimeUnit.MINUTES).build() - // Register the realtime listener work request. - WorkManager.getInstance(this).enqueueUniquePeriodicWork(getString(R.string.register_listener_work_request), ExistingPeriodicWorkPolicy.REPLACE, registerRealtimeListenerWorkRequest) + // Register the realtime listener work request. + WorkManager.getInstance(this).enqueueUniquePeriodicWork(getString(R.string.register_listener_work_request), ExistingPeriodicWorkPolicy.REPLACE, registerRealtimeListenerWorkRequest) + } + } else { // Realtime monitoring is disabled. This can happen if the restart listener work request fires after realtime monitoring has been disabled. + // Cancel the realtime listener work request. + WorkManager.getInstance(applicationContext).cancelUniqueWork(applicationContext.getString(R.string.register_listener_work_request)) } // Return a sticky service. @@ -207,7 +230,99 @@ class RealtimeMonitoringService : Service() { telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE) // Listen for changes to the phone state. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31. - telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) + telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE or PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) + } + } + + fun populateNotification() { + // Get the list of current notifications. + val activeNotificationsArray = notificationManager.activeNotifications + + // Check to see if there is a current notification. + val noCurrentNotification = activeNotificationsArray.isEmpty() + + // Populate the notification according to the security status. + if ((voiceNetworkSecurityStatus == ProtocolHelper.ANTIQUATED) || (dataNetworkSecurityStatus == ProtocolHelper.ANTIQUATED)) { // This is an antiquated network. + // Only update the notification if the network status has changed. + if ((currentStatus != ANTIQUATED_NETWORK) || noCurrentNotification) { + // Create an antiquated network notification builder. + val antiquatedNetworkNotificationBuilder = Notification.Builder(applicationContext, ANTIQUATED_NETWORK) + + // Set the notification to open Privacy Cell. + antiquatedNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent) + + // Set the notification text. + antiquatedNetworkNotificationBuilder.setContentText(getString(R.string.antiquated_network)) + + // Set the notification icon. + antiquatedNetworkNotificationBuilder.setSmallIcon(R.drawable.antiquated_notification_enabled) + + // Set the color. + antiquatedNetworkNotificationBuilder.setColor(getColor(R.color.red_notification_icon)) + + // Prevent swiping to dismiss the notification. This no longer works (except on the lock screen) in API >= 34. + antiquatedNetworkNotificationBuilder.setOngoing(true) + + // Update the notification. + notificationManager.notify(NOTIFICATION_ID, antiquatedNetworkNotificationBuilder.build()) + + // Store the new network status. + currentStatus = ANTIQUATED_NETWORK + } + } else if ((voiceNetworkSecurityStatus == ProtocolHelper.INSECURE) || (dataNetworkSecurityStatus == ProtocolHelper.INSECURE)) { // This is an insecure network. + // Only update the notification if the network status has changed. + if ((currentStatus != INSECURE_NETWORK) || noCurrentNotification) { + // Create an insecure network notification builder. + val insecureNetworkNotificationBuilder = Notification.Builder(applicationContext, INSECURE_NETWORK) + + // Set the notification to open Privacy Cell. + insecureNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent) + + // Set the notification text. + insecureNetworkNotificationBuilder.setContentText(getString(R.string.insecure_network)) + + // Set the notification icon. + insecureNetworkNotificationBuilder.setSmallIcon(R.drawable.insecure_notification_enabled) + + // Set the color. + insecureNetworkNotificationBuilder.setColor(getColor(R.color.yellow_notification_icon)) + + // Prevent swiping to dismiss the notification. This no longer works (except on the lock screen) in API >= 34. + insecureNetworkNotificationBuilder.setOngoing(true) + + // Update the notification. + notificationManager.notify(NOTIFICATION_ID, insecureNetworkNotificationBuilder.build()) + + // Store the new network status. + currentStatus = INSECURE_NETWORK + } + } else { // This is a secure network. + // Only update the notification if the network status has changed. + if ((currentStatus != SECURE_NETWORK) || noCurrentNotification) { + // Create a secure network notification builder. + val secureNetworkNotificationBuilder = Notification.Builder(applicationContext, SECURE_NETWORK) + + // Set the notification to open Privacy Cell. + secureNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent) + + // Set the notification text. + secureNetworkNotificationBuilder.setContentText(getString(R.string.secure_network)) + + // Set the notification icon. + secureNetworkNotificationBuilder.setSmallIcon(R.drawable.secure_notification_enabled) + + // Set the color. + secureNetworkNotificationBuilder.setColor(getColor(R.color.blue_icon)) + + // Prevent swiping to dismiss the notification. This no longer works (except on the lock screen) in API >= 34. + secureNetworkNotificationBuilder.setOngoing(true) + + // Update the notification. + notificationManager.notify(NOTIFICATION_ID, secureNetworkNotificationBuilder.build()) + + // Store the new network status. + currentStatus = SECURE_NETWORK + } } } -} \ No newline at end of file +}