Fix Android 12 preventing the restarting of the monitoring service. https://redmine...
[PrivacyCell.git] / app / src / main / java / com / stoutner / privacycell / services / RealtimeMonitoringService.kt
index 663c178016e701f2ea4650feb72745cc93b53dad..7d467dd1d2576165407af96325896c9d6cf0d90d 100644 (file)
@@ -19,6 +19,7 @@
 
 package com.stoutner.privacycell.services
 
+import android.Manifest
 import android.app.Notification
 import android.app.NotificationChannel
 import android.app.NotificationChannelGroup
@@ -27,32 +28,51 @@ 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
+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.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.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.
+        // 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 UNKNOWN_NETWORK = "unknown_network"
     }
 
-    override fun onBind(intent: Intent?): IBinder? {
-        // Do nothing.
-        return null
+    // 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 onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+    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 notification manager.
         val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
 
@@ -60,8 +80,8 @@ class RealtimeMonitoringService : Service() {
         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_DEFAULT)
-        val insecureNetworkChannel = NotificationChannel(INSECURE_NETWORK, getString(R.string.insecure_network_channel), NotificationManager.IMPORTANCE_DEFAULT)
+        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 unknownNetworkChannel = NotificationChannel(UNKNOWN_NETWORK, getString(R.string.unknown_network_channel), NotificationManager.IMPORTANCE_LOW)
 
         // Set the notification channel group.
@@ -107,14 +127,8 @@ class RealtimeMonitoringService : Service() {
         // Start the foreground notification.
         startForeground(NOTIFICATION_ID, notificationBuilder.build())
 
-        // Get a handle for the telephony manager and the context.
-        val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
-
-        // Initialize the current status.
-        var currentStatus = ""
-
-        // 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) {
                 // Populate the notification according to the network type.
                 if (telephonyDisplayInfo.networkType == TelephonyManager.NETWORK_TYPE_NR) {  // This is a secure 5G NR SA network.
@@ -143,7 +157,7 @@ class RealtimeMonitoringService : Service() {
                     }
                 } else {  // This is not a secure 5G NR SA 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)
 
@@ -167,9 +181,33 @@ class RealtimeMonitoringService : Service() {
                     }
                 }
             }
-        }, 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