/*
- * Copyright © 2021 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2021-2022 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Cell <https://www.stoutner.com/privacy-cell>.
*
* along with Privacy Cell. If not, see <http://www.gnu.org/licenses/>.
*/
+// 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.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
+import android.telephony.PhoneStateListener // This can be replaced by `TelephonyCallback` once the minimum API >= 31.
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.workers.RegisterRealtimeListener
+import com.stoutner.privacycell.workers.RegisterRealtimeListenerWorker
import java.util.concurrent.TimeUnit
// 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 lateinit var phoneStateListener: PhoneStateListener // The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
inner class ServiceBinder : Binder() {
// Get a copy of this service as a binder.
fun getService(): RealtimeMonitoringService = this@RealtimeMonitoringService
}
- override fun onBind(intent: Intent): IBinder {
+ 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 {
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ // Get a handle for the shared preferences.
+ val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
+
// Get a handle for the notification manager.
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// 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 notifications to be public for the secure and insecure networks.
+ // 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.
notificationBuilder.setContentText(getString(R.string.unknown_network))
// Set the notification icon.
- notificationBuilder.setSmallIcon(R.drawable.insecure_notification)
+ notificationBuilder.setSmallIcon(R.drawable.antiquated_notification_enabled)
// Set the color.
- notificationBuilder.setColor(getColor(R.color.red_text))
+ notificationBuilder.setColor(getColor(R.color.red_notification_icon))
// Start the foreground notification.
startForeground(NOTIFICATION_ID, notificationBuilder.build())
- // Register the telephony manager listener.
- registerTelephonyManagerListener()
-
- // Create a register realtime listener work request that fires every 15 minutes with a 1 minute initial delay.
- val registerRealtimeListenerWorkRequest = PeriodicWorkRequestBuilder<RegisterRealtimeListener>(15, TimeUnit.MINUTES).setInitialDelay(1, TimeUnit.MINUTES).build()
-
- // Register the realtime listener work request.
- WorkManager.getInstance(this).enqueueUniquePeriodicWork(getString(R.string.register_listener_work_request), ExistingPeriodicWorkPolicy.REPLACE, registerRealtimeListenerWorkRequest)
-
- // Return a sticky service.
- return START_STICKY
- }
-
- fun registerTelephonyManagerListener() {
- // Get a handle for the telephony manager.
- val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
-
- // Get a handle for the notification manager.
- val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
-
- // Create an intent to open Privacy Cell.
- val privacyCellIntent = Intent(this, PrivacyCellActivity::class.java)
-
- // Create a pending intent from the Privacy Cell intent.
- val privacyCellPendingIntent = PendingIntent.getActivity(this, 0, privacyCellIntent, PendingIntent.FLAG_IMMUTABLE)
-
- // Listen for changes to the phone state.
- telephonyManager.listen(object : PhoneStateListener() {
+ // Define the phone state listener. The `PhoneStateListener` can be replaced by `TelephonyCallback` once the minimum API >= 31.
+ phoneStateListener = object : PhoneStateListener() {
override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) {
+ // Get the consider 3G antiquated preference.
+ val consider3gAntiquated = sharedPreferences.getBoolean(getString(R.string.consider_3g_antiquated_key), false)
+
// Populate the notification according to the network type.
- if (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_NR) { // This is a secure 5G NR SA network.
+ if ((telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_NR) || (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_IWLAN) ||
+ (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_UNKNOWN)) { // 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.
secureNetworkNotificationBuilder.setContentText(getString(R.string.secure_network))
// Set the notification icon.
- secureNetworkNotificationBuilder.setSmallIcon(R.drawable.secure_notification)
+ secureNetworkNotificationBuilder.setSmallIcon(R.drawable.secure_notification_enabled)
// Set the color.
- secureNetworkNotificationBuilder.setColor(getColor(R.color.blue_text))
+ secureNetworkNotificationBuilder.setColor(getColor(R.color.blue_icon))
// Update the notification.
notificationManager.notify(NOTIFICATION_ID, secureNetworkNotificationBuilder.build())
// Store the new network status.
currentStatus = SECURE_NETWORK
}
- } else { // This is not a secure 5G NR SA network.
+ } else if ((telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_LTE) || (!consider3gAntiquated && (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_1xRTT ||
+ (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_EVDO_0) || (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_EVDO_A) ||
+ (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_EVDO_B) || (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_EHRPD) ||
+ (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_UMTS) || (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_TD_SCDMA) ||
+ (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_HSDPA) || (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_HSUPA) ||
+ (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_HSPA) || (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_HSPAP)))) {
+ // This is an insecure network.
// Only update the notification if the network status has changed.
- if (currentStatus !=INSECURE_NETWORK) {
+ if (currentStatus != INSECURE_NETWORK) {
// Create an insecure network notification builder.
val insecureNetworkNotificationBuilder = Notification.Builder(applicationContext, INSECURE_NETWORK)
insecureNetworkNotificationBuilder.setContentText(getString(R.string.insecure_network))
// Set the notification icon.
- insecureNetworkNotificationBuilder.setSmallIcon(R.drawable.insecure_notification)
+ insecureNetworkNotificationBuilder.setSmallIcon(R.drawable.insecure_notification_enabled)
// Set the color.
- insecureNetworkNotificationBuilder.setColor(getColor(R.color.red_text))
+ 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 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
+ }
}
}
- }, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
+ }
+
+ // 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<RegisterRealtimeListenerWorker>(1, TimeUnit.HOURS).build()
+
+ // Register the realtime listener work request.
+ WorkManager.getInstance(this).enqueueUniquePeriodicWork(getString(R.string.register_listener_work_request), ExistingPeriodicWorkPolicy.REPLACE, registerRealtimeListenerWorkRequest)
+ }
+
+ // 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_DISPLAY_INFO_CHANGED)
+ }
}
}
\ No newline at end of file