/* * Copyright © 2021-2022 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)) // 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 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() // 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() { // 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) { // 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)) // 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) { // 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)) // 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) { // 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)) // Update the notification. notificationManager.notify(NOTIFICATION_ID, secureNetworkNotificationBuilder.build()) // Store the new network status. currentStatus = SECURE_NETWORK } } } }