]> gitweb.stoutner.com Git - PrivacyCell.git/blob - app/src/main/java/com/stoutner/privacycell/services/RealtimeMonitoringService.kt
Release 1.10.
[PrivacyCell.git] / app / src / main / java / com / stoutner / privacycell / services / RealtimeMonitoringService.kt
1 /*
2  * Copyright 2021-2023 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Cell <https://www.stoutner.com/privacy-cell>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 // The suppression of deprecation lint can be removed once the minimum API >= 31.
21 @file:Suppress("DEPRECATION")
22
23 package com.stoutner.privacycell.services
24
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
41
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
47
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
52
53 import java.util.concurrent.TimeUnit
54
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"
59
60 class RealtimeMonitoringService : Service() {
61     companion object {
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"
66     }
67
68     // Define the class variables.
69     private var currentStatus = ""
70     private var voiceNetworkSecurityStatus = ProtocolHelper.UNPOPULATED
71     private var dataNetworkSecurityStatus = ProtocolHelper.UNPOPULATED
72
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
77
78     inner class ServiceBinder : Binder() {
79         // Get a copy of this service as a binder.
80         fun getService(): RealtimeMonitoringService = this@RealtimeMonitoringService
81     }
82
83     override fun onBind(intent: Intent?): IBinder {
84         // Return a copy of the service binder.
85         return ServiceBinder()
86     }
87
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)
91
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
97
98             // Create a notification channel group.
99             notificationManager.createNotificationChannelGroup(NotificationChannelGroup(REALTIME_MONITORING, getString(R.string.realtime_monitoring)))
100
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)
106
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
112
113             // Disable the notification dots.
114             secureNetworkChannel.setShowBadge(false)
115             insecureNetworkChannel.setShowBadge(false)
116             antiquatedNetworkChannel.setShowBadge(false)
117             unknownNetworkChannel.setShowBadge(false)
118
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
123
124             // Create the notification channels.
125             notificationManager.createNotificationChannel(secureNetworkChannel)
126             notificationManager.createNotificationChannel(insecureNetworkChannel)
127             notificationManager.createNotificationChannel(antiquatedNetworkChannel)
128             notificationManager.createNotificationChannel(unknownNetworkChannel)
129
130             // Create a notification builder.
131             val notificationBuilder = Notification.Builder(this, UNKNOWN_NETWORK)
132
133             // Create an intent to open Privacy Cell.
134             val privacyCellIntent = Intent(this, PrivacyCellActivity::class.java)
135
136             // Create a pending intent from the Privacy Cell intent.
137             privacyCellPendingIntent = PendingIntent.getActivity(this, 0, privacyCellIntent, PendingIntent.FLAG_IMMUTABLE)
138
139             // Set the notification to open Privacy Cell.
140             notificationBuilder.setContentIntent(privacyCellPendingIntent)
141
142             // Set the notification text.
143             notificationBuilder.setContentText(getString(R.string.unknown_network))
144
145             // Set the notification icon.
146             notificationBuilder.setSmallIcon(R.drawable.antiquated_notification_enabled)
147
148             // Set the color.
149             notificationBuilder.setColor(getColor(R.color.red_notification_icon))
150
151             // Prevent swiping to dismiss the notification.  This no longer works (except on the lock screen) in API >= 34.
152             notificationBuilder.setOngoing(true)
153
154             // Start the foreground notification.
155             startForeground(NOTIFICATION_ID, notificationBuilder.build())
156
157             // Instantiate the protocol helper.
158             val protocolHelper = ProtocolHelper()
159
160             // Get a handle for the telephony manager.
161             val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
162
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]
171
172                         // Get the consider 3G antiquated preference.
173                         val consider3gAntiquated = sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false)
174
175                         // Update the voice network security status.
176                         voiceNetworkSecurityStatus = protocolHelper.checkNetwork(networkRegistrationInfo.accessNetworkTechnology, consider3gAntiquated)
177
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)
183                     }
184                 }
185
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)
192
193                         // Update the data network security status.
194                         dataNetworkSecurityStatus = protocolHelper.checkNetwork(telephonyDisplayInfo.networkType, consider3gAntiquated)
195
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)
201                     }
202                 }
203             }
204
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 fifteen minutes.
208                 // This periodic request will fire shortly after being created and will reregister the listener if it gets garbage collected.
209                 val registerRealtimeListenerWorkRequest = PeriodicWorkRequestBuilder<RegisterRealtimeListenerWorker>(15, TimeUnit.MINUTES).build()
210
211                 // Register the realtime listener work request.
212                 WorkManager.getInstance(this).enqueueUniquePeriodicWork(getString(R.string.register_listener_work_request), ExistingPeriodicWorkPolicy.REPLACE, registerRealtimeListenerWorkRequest)
213             }
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))
217         }
218
219         // Return a sticky service.
220         return START_STICKY
221     }
222
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
228
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)
231
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)
234         }
235     }
236
237     fun populateNotification() {
238         // Get the list of current notifications.
239         val activeNotificationsArray = notificationManager.activeNotifications
240
241         // Check to see if there is a current notification.
242         val noCurrentNotification = activeNotificationsArray.isEmpty()
243
244         // Populate the notification according to the security status.
245         if ((voiceNetworkSecurityStatus == ProtocolHelper.ANTIQUATED) || (dataNetworkSecurityStatus == ProtocolHelper.ANTIQUATED)) {  // This is an antiquated network.
246             // Only update the notification if the network status has changed.
247             if ((currentStatus != ANTIQUATED_NETWORK) || noCurrentNotification) {
248                 // Create an antiquated network notification builder.
249                 val antiquatedNetworkNotificationBuilder = Notification.Builder(applicationContext, ANTIQUATED_NETWORK)
250
251                 // Set the notification to open Privacy Cell.
252                 antiquatedNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent)
253
254                 // Set the notification text.
255                 antiquatedNetworkNotificationBuilder.setContentText(getString(R.string.antiquated_network))
256
257                 // Set the notification icon.
258                 antiquatedNetworkNotificationBuilder.setSmallIcon(R.drawable.antiquated_notification_enabled)
259
260                 // Set the color.
261                 antiquatedNetworkNotificationBuilder.setColor(getColor(R.color.red_notification_icon))
262
263                 // Prevent swiping to dismiss the notification.  This no longer works (except on the lock screen) in API >= 34.
264                 antiquatedNetworkNotificationBuilder.setOngoing(true)
265
266                 // Update the notification.
267                 notificationManager.notify(NOTIFICATION_ID, antiquatedNetworkNotificationBuilder.build())
268
269                 // Store the new network status.
270                 currentStatus = ANTIQUATED_NETWORK
271             }
272         } else if ((voiceNetworkSecurityStatus == ProtocolHelper.INSECURE) || (dataNetworkSecurityStatus == ProtocolHelper.INSECURE)) {  // This is an insecure network.
273             // Only update the notification if the network status has changed.
274             if ((currentStatus != INSECURE_NETWORK) || noCurrentNotification) {
275                 // Create an insecure network notification builder.
276                 val insecureNetworkNotificationBuilder = Notification.Builder(applicationContext, INSECURE_NETWORK)
277
278                 // Set the notification to open Privacy Cell.
279                 insecureNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent)
280
281                 // Set the notification text.
282                 insecureNetworkNotificationBuilder.setContentText(getString(R.string.insecure_network))
283
284                 // Set the notification icon.
285                 insecureNetworkNotificationBuilder.setSmallIcon(R.drawable.insecure_notification_enabled)
286
287                 // Set the color.
288                 insecureNetworkNotificationBuilder.setColor(getColor(R.color.yellow_notification_icon))
289
290                 // Prevent swiping to dismiss the notification.  This no longer works (except on the lock screen) in API >= 34.
291                 insecureNetworkNotificationBuilder.setOngoing(true)
292
293                 // Update the notification.
294                 notificationManager.notify(NOTIFICATION_ID, insecureNetworkNotificationBuilder.build())
295
296                 // Store the new network status.
297                 currentStatus = INSECURE_NETWORK
298             }
299         } else {  // This is a secure network.
300             // Only update the notification if the network status has changed.
301             if ((currentStatus != SECURE_NETWORK) || noCurrentNotification) {
302                 // Create a secure network notification builder.
303                 val secureNetworkNotificationBuilder = Notification.Builder(applicationContext, SECURE_NETWORK)
304
305                 // Set the notification to open Privacy Cell.
306                 secureNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent)
307
308                 // Set the notification text.
309                 secureNetworkNotificationBuilder.setContentText(getString(R.string.secure_network))
310
311                 // Set the notification icon.
312                 secureNetworkNotificationBuilder.setSmallIcon(R.drawable.secure_notification_enabled)
313
314                 // Set the color.
315                 secureNetworkNotificationBuilder.setColor(getColor(R.color.blue_icon))
316
317                 // Prevent swiping to dismiss the notification.  This no longer works (except on the lock screen) in API >= 34.
318                 secureNetworkNotificationBuilder.setOngoing(true)
319
320                 // Update the notification.
321                 notificationManager.notify(NOTIFICATION_ID, secureNetworkNotificationBuilder.build())
322
323                 // Store the new network status.
324                 currentStatus = SECURE_NETWORK
325             }
326         }
327     }
328 }