/* * 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 . */ // The suppression of deprecation lint can be removed once the minimum API >= 31. @file:Suppress("DEPRECATION") package com.stoutner.privacycell.services import android.Manifest import android.app.Notification import android.app.NotificationChannel import android.app.NotificationChannelGroup import android.app.NotificationManager import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent 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 // Define the class constants. const val REALTIME_MONITORING = "realtime_monitoring" const val NOTIFICATION_ID = 1 const val UNKNOWN_NETWORK = "unknown_network" class RealtimeMonitoringService : Service() { companion object { // 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. fun getService(): RealtimeMonitoringService = this@RealtimeMonitoringService } override fun onBind(intent: Intent?): IBinder { // Return a copy of the service binder. return ServiceBinder() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // Get a handle for the shared preferences. val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) // 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 // Create a notification channel group. notificationManager.createNotificationChannelGroup(NotificationChannelGroup(REALTIME_MONITORING, getString(R.string.realtime_monitoring))) // 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 notification channel group. secureNetworkChannel.group = REALTIME_MONITORING insecureNetworkChannel.group = REALTIME_MONITORING antiquatedNetworkChannel.group = REALTIME_MONITORING unknownNetworkChannel.group = REALTIME_MONITORING // Disable the notification dots. secureNetworkChannel.setShowBadge(false) insecureNetworkChannel.setShowBadge(false) antiquatedNetworkChannel.setShowBadge(false) unknownNetworkChannel.setShowBadge(false) // Set the primary channel notifications to be public. secureNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC insecureNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC antiquatedNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC // Create the notification channels. notificationManager.createNotificationChannel(secureNetworkChannel) notificationManager.createNotificationChannel(insecureNetworkChannel) notificationManager.createNotificationChannel(antiquatedNetworkChannel) notificationManager.createNotificationChannel(unknownNetworkChannel) // Create a notification builder. val notificationBuilder = Notification.Builder(this, UNKNOWN_NETWORK) // Create an intent to open Privacy Cell. val privacyCellIntent = Intent(this, PrivacyCellActivity::class.java) // Create a pending intent from the Privacy Cell intent. privacyCellPendingIntent = PendingIntent.getActivity(this, 0, privacyCellIntent, PendingIntent.FLAG_IMMUTABLE) // Set the notification to open Privacy Cell. notificationBuilder.setContentIntent(privacyCellPendingIntent) // Set the notification text. notificationBuilder.setContentText(getString(R.string.unknown_network)) // Set the notification icon. notificationBuilder.setSmallIcon(R.drawable.antiquated_notification_enabled) // Set the color. notificationBuilder.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. notificationBuilder.setOngoing(true) // Start the foreground notification. startForeground(NOTIFICATION_ID, notificationBuilder.build()) // Instantiate the protocol helper. val protocolHelper = ProtocolHelper() // Get a handle for the telephony manager. val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager // 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) } } @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 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) } } 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. return START_STICKY } fun registerTelephonyManagerListener() { // Check to see if the read phone state permission has been granted. if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { // Get a handle for the telephony manager. val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager // Cancel the current listener if it exists. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31. 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_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 } } } }