2 * Copyright 2021-2022 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Cell <https://www.stoutner.com/privacy-cell>.
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.
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.
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/>.
20 // The suppression of deprecation lint can be removed once the minimum API >= 31.
21 @file:Suppress("DEPRECATION")
23 package com.stoutner.privacycell.services
25 import android.Manifest
26 import android.app.Notification
27 import android.app.NotificationChannel
28 import android.app.NotificationChannelGroup
29 import android.app.NotificationManager
30 import android.app.PendingIntent
31 import android.app.Service
32 import android.content.Context
33 import android.content.Intent
34 import android.content.pm.PackageManager
35 import android.os.Binder
36 import android.os.IBinder
37 import android.telephony.PhoneStateListener // This can be replaced by `TelephonyCallback` once the minimum API >= 31.
38 import android.telephony.ServiceState
39 import android.telephony.TelephonyDisplayInfo
40 import android.telephony.TelephonyManager
42 import androidx.core.app.ActivityCompat
43 import androidx.preference.PreferenceManager
44 import androidx.work.ExistingPeriodicWorkPolicy
45 import androidx.work.PeriodicWorkRequestBuilder
46 import androidx.work.WorkManager
48 import com.stoutner.privacycell.R
49 import com.stoutner.privacycell.activities.PrivacyCellActivity
50 import com.stoutner.privacycell.helpers.ProtocolHelper
51 import com.stoutner.privacycell.workers.RegisterRealtimeListenerWorker
53 import java.util.concurrent.TimeUnit
55 // Define the class constants.
56 const val REALTIME_MONITORING = "realtime_monitoring"
57 const val NOTIFICATION_ID = 1
58 const val UNKNOWN_NETWORK = "unknown_network"
60 class RealtimeMonitoringService : Service() {
62 // Define the public constants. These are used in the settings fragment to launch intents to edit the sound that plays for each channel.
63 const val SECURE_NETWORK = "secure_network"
64 const val INSECURE_NETWORK = "insecure_network"
65 const val ANTIQUATED_NETWORK = "antiquated_network"
68 // Define the class variables.
69 private var currentStatus = ""
70 private var voiceNetworkSecurityStatus = ProtocolHelper.UNPOPULATED
71 private var dataNetworkSecurityStatus = ProtocolHelper.UNPOPULATED
73 // Declare the class variables.
74 private lateinit var notificationManager: NotificationManager
75 private lateinit var phoneStateListener: PhoneStateListener // The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
76 private lateinit var privacyCellPendingIntent: PendingIntent
78 inner class ServiceBinder : Binder() {
79 // Get a copy of this service as a binder.
80 fun getService(): RealtimeMonitoringService = this@RealtimeMonitoringService
83 override fun onBind(intent: Intent?): IBinder {
84 // Return a copy of the service binder.
85 return ServiceBinder()
88 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
89 // Get a handle for the shared preferences.
90 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
92 // Check to see if realtime monitoring is enabled. Sometimes the shared preferences can't return a value in time, because Android sucks.
93 // So, the default value is set to true, which is the safest value if the shared preferences can't be queried.
94 if (sharedPreferences.getBoolean(applicationContext.getString(R.string.realtime_monitoring_key), true)) { // Realtime monitoring is enabled.
95 // Get a handle for the notification manager.
96 notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
98 // Create a notification channel group.
99 notificationManager.createNotificationChannelGroup(NotificationChannelGroup(REALTIME_MONITORING, getString(R.string.realtime_monitoring)))
101 // Prepare the notification channels.
102 val secureNetworkChannel = NotificationChannel(SECURE_NETWORK, getString(R.string.secure_network_channel), NotificationManager.IMPORTANCE_HIGH)
103 val insecureNetworkChannel = NotificationChannel(INSECURE_NETWORK, getString(R.string.insecure_network_channel), NotificationManager.IMPORTANCE_HIGH)
104 val antiquatedNetworkChannel = NotificationChannel(ANTIQUATED_NETWORK, getString(R.string.antiquated_network_channel), NotificationManager.IMPORTANCE_HIGH)
105 val unknownNetworkChannel = NotificationChannel(UNKNOWN_NETWORK, getString(R.string.unknown_network_channel), NotificationManager.IMPORTANCE_LOW)
107 // Set the notification channel group.
108 secureNetworkChannel.group = REALTIME_MONITORING
109 insecureNetworkChannel.group = REALTIME_MONITORING
110 antiquatedNetworkChannel.group = REALTIME_MONITORING
111 unknownNetworkChannel.group = REALTIME_MONITORING
113 // Disable the notification dots.
114 secureNetworkChannel.setShowBadge(false)
115 insecureNetworkChannel.setShowBadge(false)
116 antiquatedNetworkChannel.setShowBadge(false)
117 unknownNetworkChannel.setShowBadge(false)
119 // Set the primary channel notifications to be public.
120 secureNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
121 insecureNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
122 antiquatedNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
124 // Create the notification channels.
125 notificationManager.createNotificationChannel(secureNetworkChannel)
126 notificationManager.createNotificationChannel(insecureNetworkChannel)
127 notificationManager.createNotificationChannel(antiquatedNetworkChannel)
128 notificationManager.createNotificationChannel(unknownNetworkChannel)
130 // Create a notification builder.
131 val notificationBuilder = Notification.Builder(this, UNKNOWN_NETWORK)
133 // Create an intent to open Privacy Cell.
134 val privacyCellIntent = Intent(this, PrivacyCellActivity::class.java)
136 // Create a pending intent from the Privacy Cell intent.
137 privacyCellPendingIntent = PendingIntent.getActivity(this, 0, privacyCellIntent, PendingIntent.FLAG_IMMUTABLE)
139 // Set the notification to open Privacy Cell.
140 notificationBuilder.setContentIntent(privacyCellPendingIntent)
142 // Set the notification text.
143 notificationBuilder.setContentText(getString(R.string.unknown_network))
145 // Set the notification icon.
146 notificationBuilder.setSmallIcon(R.drawable.antiquated_notification_enabled)
149 notificationBuilder.setColor(getColor(R.color.red_notification_icon))
151 // Prevent swiping to dismiss the notification.
152 notificationBuilder.setOngoing(true)
154 // Start the foreground notification.
155 startForeground(NOTIFICATION_ID, notificationBuilder.build())
157 // Instantiate the protocol helper.
158 val protocolHelper = ProtocolHelper()
160 // Get a handle for the telephony manager.
161 val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
163 // Define the phone state listener. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
164 phoneStateListener = object : PhoneStateListener() {
165 @Deprecated("Deprecated in Java")
166 override fun onServiceStateChanged(serviceState: ServiceState) { // Update the voice network status.
167 // Check to see if realtime monitoring is enabled. Sometimes the system keeps running the service even when it is supposed to shut down.
168 if (sharedPreferences.getBoolean(applicationContext.getString(R.string.realtime_monitoring_key), true)) { // Realtime monitoring is enabled.
169 // 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).
170 val networkRegistrationInfo = serviceState.networkRegistrationInfoList[1]
172 // Get the consider 3G antiquated preference.
173 val consider3gAntiquated = sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false)
175 // Update the voice network security status.
176 voiceNetworkSecurityStatus = protocolHelper.checkNetwork(networkRegistrationInfo.accessNetworkTechnology, consider3gAntiquated)
178 // Populate the notification.
179 populateNotification()
180 } else { // Realtime monitoring is disabled.
181 // Cancel the current listener if it exists. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
182 telephonyManager.listen(phoneStateListener, LISTEN_NONE)
186 @Deprecated("Deprecated in Java")
187 override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) { // Update the data network status.
188 // Check to see if realtime monitoring is enabled. Sometimes the system keeps running the service even when it is supposed to shut down.
189 if (sharedPreferences.getBoolean(applicationContext.getString(R.string.realtime_monitoring_key), true)) { // Realtime monitoring is enabled.
190 // Get the consider 3G antiquated preference.
191 val consider3gAntiquated = sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false)
193 // Update the data network security status.
194 dataNetworkSecurityStatus = protocolHelper.checkNetwork(telephonyDisplayInfo.networkType, consider3gAntiquated)
196 // Populate the notification.
197 populateNotification()
198 } else { // Realtime monitoring is disabled.
199 // Cancel the current listener if it exists. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
200 telephonyManager.listen(phoneStateListener, LISTEN_NONE)
205 // Check to see if the read phone state permission has been granted.
206 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
207 // Create a register realtime listener work request that fires every hour.
208 // 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.
209 val registerRealtimeListenerWorkRequest = PeriodicWorkRequestBuilder<RegisterRealtimeListenerWorker>(1, TimeUnit.HOURS).build()
211 // Register the realtime listener work request.
212 WorkManager.getInstance(this).enqueueUniquePeriodicWork(getString(R.string.register_listener_work_request), ExistingPeriodicWorkPolicy.REPLACE, registerRealtimeListenerWorkRequest)
214 } else { // Realtime monitoring is disabled. This can happen if the restart listener work request fires after realtime monitoring has been disabled.
215 // Cancel the realtime listener work request.
216 WorkManager.getInstance(applicationContext).cancelUniqueWork(applicationContext.getString(R.string.register_listener_work_request))
219 // Return a sticky service.
223 fun registerTelephonyManagerListener() {
224 // Check to see if the read phone state permission has been granted.
225 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
226 // Get a handle for the telephony manager.
227 val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
229 // Cancel the current listener if it exists. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
230 telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE)
232 // Listen for changes to the phone state. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
233 telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE or PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
237 fun populateNotification() {
238 // Populate the notification according to the security status.
239 if ((voiceNetworkSecurityStatus == ProtocolHelper.ANTIQUATED) || (dataNetworkSecurityStatus == ProtocolHelper.ANTIQUATED)) { // This is an antiquated network.
240 // Only update the notification if the network status has changed.
241 if (currentStatus != ANTIQUATED_NETWORK) {
242 // Create an antiquated network notification builder.
243 val antiquatedNetworkNotificationBuilder = Notification.Builder(applicationContext, ANTIQUATED_NETWORK)
245 // Set the notification to open Privacy Cell.
246 antiquatedNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent)
248 // Set the notification text.
249 antiquatedNetworkNotificationBuilder.setContentText(getString(R.string.antiquated_network))
251 // Set the notification icon.
252 antiquatedNetworkNotificationBuilder.setSmallIcon(R.drawable.antiquated_notification_enabled)
255 antiquatedNetworkNotificationBuilder.setColor(getColor(R.color.red_notification_icon))
257 // Prevent swiping to dismiss the notification.
258 antiquatedNetworkNotificationBuilder.setOngoing(true)
260 // Update the notification.
261 notificationManager.notify(NOTIFICATION_ID, antiquatedNetworkNotificationBuilder.build())
263 // Store the new network status.
264 currentStatus = ANTIQUATED_NETWORK
266 } else if ((voiceNetworkSecurityStatus == ProtocolHelper.INSECURE) || (dataNetworkSecurityStatus == ProtocolHelper.INSECURE)) { // This is an insecure network.
267 // Only update the notification if the network status has changed.
268 if (currentStatus != INSECURE_NETWORK) {
269 // Create an insecure network notification builder.
270 val insecureNetworkNotificationBuilder = Notification.Builder(applicationContext, INSECURE_NETWORK)
272 // Set the notification to open Privacy Cell.
273 insecureNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent)
275 // Set the notification text.
276 insecureNetworkNotificationBuilder.setContentText(getString(R.string.insecure_network))
278 // Set the notification icon.
279 insecureNetworkNotificationBuilder.setSmallIcon(R.drawable.insecure_notification_enabled)
282 insecureNetworkNotificationBuilder.setColor(getColor(R.color.yellow_notification_icon))
284 // Prevent swiping to dismiss the notification.
285 insecureNetworkNotificationBuilder.setOngoing(true)
287 // Update the notification.
288 notificationManager.notify(NOTIFICATION_ID, insecureNetworkNotificationBuilder.build())
290 // Store the new network status.
291 currentStatus = INSECURE_NETWORK
293 } else { // This is a secure network.
294 // Only update the notification if the network status has changed.
295 if (currentStatus != SECURE_NETWORK) {
296 // Create a secure network notification builder.
297 val secureNetworkNotificationBuilder = Notification.Builder(applicationContext, SECURE_NETWORK)
299 // Set the notification to open Privacy Cell.
300 secureNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent)
302 // Set the notification text.
303 secureNetworkNotificationBuilder.setContentText(getString(R.string.secure_network))
305 // Set the notification icon.
306 secureNetworkNotificationBuilder.setSmallIcon(R.drawable.secure_notification_enabled)
309 secureNetworkNotificationBuilder.setColor(getColor(R.color.blue_icon))
311 // Prevent swiping to dismiss the notification.
312 secureNetworkNotificationBuilder.setOngoing(true)
314 // Update the notification.
315 notificationManager.notify(NOTIFICATION_ID, secureNetworkNotificationBuilder.build())
317 // Store the new network status.
318 currentStatus = SECURE_NETWORK