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.TelephonyDisplayInfo
39 import android.telephony.TelephonyManager
41 import androidx.core.app.ActivityCompat
42 import androidx.preference.PreferenceManager
43 import androidx.work.ExistingPeriodicWorkPolicy
44 import androidx.work.PeriodicWorkRequestBuilder
45 import androidx.work.WorkManager
47 import com.stoutner.privacycell.R
48 import com.stoutner.privacycell.activities.PrivacyCellActivity
49 import com.stoutner.privacycell.workers.RegisterRealtimeListenerWorker
51 import java.util.concurrent.TimeUnit
53 // Define the class constants.
54 const val REALTIME_MONITORING = "realtime_monitoring"
55 const val NOTIFICATION_ID = 1
56 const val UNKNOWN_NETWORK = "unknown_network"
58 class RealtimeMonitoringService : Service() {
60 // Define the public constants. These are used in the settings fragment to launch intents to edit the sound that plays for each channel.
61 const val SECURE_NETWORK = "secure_network"
62 const val INSECURE_NETWORK = "insecure_network"
63 const val ANTIQUATED_NETWORK = "antiquated_network"
66 // Define the class variables.
67 private var currentStatus = ""
68 private lateinit var phoneStateListener: PhoneStateListener // The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
70 inner class ServiceBinder : Binder() {
71 // Get a copy of this service as a binder.
72 fun getService(): RealtimeMonitoringService = this@RealtimeMonitoringService
75 override fun onBind(intent: Intent?): IBinder {
76 // Return a copy of the service binder.
77 return ServiceBinder()
80 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
81 // Get a handle for the shared preferences.
82 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
84 // Get a handle for the notification manager.
85 val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
87 // Create a notification channel group.
88 notificationManager.createNotificationChannelGroup(NotificationChannelGroup(REALTIME_MONITORING, getString(R.string.realtime_monitoring)))
90 // Prepare the notification channels.
91 val secureNetworkChannel = NotificationChannel(SECURE_NETWORK, getString(R.string.secure_network_channel), NotificationManager.IMPORTANCE_HIGH)
92 val insecureNetworkChannel = NotificationChannel(INSECURE_NETWORK, getString(R.string.insecure_network_channel), NotificationManager.IMPORTANCE_HIGH)
93 val antiquatedNetworkChannel = NotificationChannel(ANTIQUATED_NETWORK, getString(R.string.antiquated_network_channel), NotificationManager.IMPORTANCE_HIGH)
94 val unknownNetworkChannel = NotificationChannel(UNKNOWN_NETWORK, getString(R.string.unknown_network_channel), NotificationManager.IMPORTANCE_LOW)
96 // Set the notification channel group.
97 secureNetworkChannel.group = REALTIME_MONITORING
98 insecureNetworkChannel.group = REALTIME_MONITORING
99 antiquatedNetworkChannel.group = REALTIME_MONITORING
100 unknownNetworkChannel.group = REALTIME_MONITORING
102 // Disable the notification dots.
103 secureNetworkChannel.setShowBadge(false)
104 insecureNetworkChannel.setShowBadge(false)
105 antiquatedNetworkChannel.setShowBadge(false)
106 unknownNetworkChannel.setShowBadge(false)
108 // Set the primary channel notifications to be public.
109 secureNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
110 insecureNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
111 antiquatedNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
113 // Create the notification channels.
114 notificationManager.createNotificationChannel(secureNetworkChannel)
115 notificationManager.createNotificationChannel(insecureNetworkChannel)
116 notificationManager.createNotificationChannel(antiquatedNetworkChannel)
117 notificationManager.createNotificationChannel(unknownNetworkChannel)
119 // Create a notification builder.
120 val notificationBuilder = Notification.Builder(this, UNKNOWN_NETWORK)
122 // Create an intent to open Privacy Cell.
123 val privacyCellIntent = Intent(this, PrivacyCellActivity::class.java)
125 // Create a pending intent from the Privacy Cell intent.
126 val privacyCellPendingIntent = PendingIntent.getActivity(this, 0, privacyCellIntent, PendingIntent.FLAG_IMMUTABLE)
128 // Set the notification to open Privacy Cell.
129 notificationBuilder.setContentIntent(privacyCellPendingIntent)
131 // Set the notification text.
132 notificationBuilder.setContentText(getString(R.string.unknown_network))
134 // Set the notification icon.
135 notificationBuilder.setSmallIcon(R.drawable.antiquated_notification_enabled)
138 notificationBuilder.setColor(getColor(R.color.red_notification_icon))
140 // Start the foreground notification.
141 startForeground(NOTIFICATION_ID, notificationBuilder.build())
143 // Define the phone state listener. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
144 phoneStateListener = object : PhoneStateListener() {
145 override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) {
146 // Get the consider 3G antiquated preference.
147 val consider3gAntiquated = sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false)
149 // Populate the notification according to the network type.
150 if ((telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_NR) || (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_IWLAN) ||
151 (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_UNKNOWN)) { // This is a secure network.
152 // Only update the notification if the network status has changed.
153 if (currentStatus != SECURE_NETWORK) {
154 // Create a secure network notification builder.
155 val secureNetworkNotificationBuilder = Notification.Builder(applicationContext, SECURE_NETWORK)
157 // Set the notification to open Privacy Cell.
158 secureNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent)
160 // Set the notification text.
161 secureNetworkNotificationBuilder.setContentText(getString(R.string.secure_network))
163 // Set the notification icon.
164 secureNetworkNotificationBuilder.setSmallIcon(R.drawable.secure_notification_enabled)
167 secureNetworkNotificationBuilder.setColor(getColor(R.color.blue_icon))
169 // Update the notification.
170 notificationManager.notify(NOTIFICATION_ID, secureNetworkNotificationBuilder.build())
172 // Store the new network status.
173 currentStatus = SECURE_NETWORK
175 } else if ((telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_LTE) || (!consider3gAntiquated && (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_1xRTT ||
176 (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_EVDO_0) || (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_EVDO_A) ||
177 (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_EVDO_B) || (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_EHRPD) ||
178 (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_UMTS) || (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_TD_SCDMA) ||
179 (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_HSDPA) || (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_HSUPA) ||
180 (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_HSPA) || (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_HSPAP)))) {
181 // This is an insecure network.
182 // Only update the notification if the network status has changed.
183 if (currentStatus != INSECURE_NETWORK) {
184 // Create an insecure network notification builder.
185 val insecureNetworkNotificationBuilder = Notification.Builder(applicationContext, INSECURE_NETWORK)
187 // Set the notification to open Privacy Cell.
188 insecureNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent)
190 // Set the notification text.
191 insecureNetworkNotificationBuilder.setContentText(getString(R.string.insecure_network))
193 // Set the notification icon.
194 insecureNetworkNotificationBuilder.setSmallIcon(R.drawable.insecure_notification_enabled)
197 insecureNetworkNotificationBuilder.setColor(getColor(R.color.yellow_notification_icon))
199 // Update the notification.
200 notificationManager.notify(NOTIFICATION_ID, insecureNetworkNotificationBuilder.build())
202 // Store the new network status.
203 currentStatus = INSECURE_NETWORK
205 } else { // This is an antiquated network.
206 // Only update the notification if the network status has changed.
207 if (currentStatus != ANTIQUATED_NETWORK) {
208 // Create an antiquated network notification builder.
209 val antiquatedNetworkNotificationBuilder = Notification.Builder(applicationContext, ANTIQUATED_NETWORK)
211 // Set the notification to open Privacy Cell.
212 antiquatedNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent)
214 // Set the notification text.
215 antiquatedNetworkNotificationBuilder.setContentText(getString(R.string.antiquated_network))
217 // Set the notification icon.
218 antiquatedNetworkNotificationBuilder.setSmallIcon(R.drawable.antiquated_notification_enabled)
221 antiquatedNetworkNotificationBuilder.setColor(getColor(R.color.red_notification_icon))
223 // Update the notification.
224 notificationManager.notify(NOTIFICATION_ID, antiquatedNetworkNotificationBuilder.build())
226 // Store the new network status.
227 currentStatus = ANTIQUATED_NETWORK
233 // Check to see if the read phone state permission has been granted.
234 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
235 // Create a register realtime listener work request that fires every hour.
236 // 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.
237 val registerRealtimeListenerWorkRequest = PeriodicWorkRequestBuilder<RegisterRealtimeListenerWorker>(1, TimeUnit.HOURS).build()
239 // Register the realtime listener work request.
240 WorkManager.getInstance(this).enqueueUniquePeriodicWork(getString(R.string.register_listener_work_request), ExistingPeriodicWorkPolicy.REPLACE, registerRealtimeListenerWorkRequest)
243 // Return a sticky service.
247 fun registerTelephonyManagerListener() {
248 // Check to see if the read phone state permission has been granted.
249 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
250 // Get a handle for the telephony manager.
251 val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
253 // Cancel the current listener if it exists. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
254 telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE)
256 // Listen for changes to the phone state. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
257 telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)