/* * Copyright © 2021 Soren Stoutner . * * This file is part of Privacy Cell . * * Privacy Cell is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Privacy Cell is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Privacy Cell. If not, see . */ package com.stoutner.privacycell.services import android.app.Notification import android.app.NotificationChannel import android.app.NotificationChannelGroup import android.app.NotificationManager import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent import android.os.Binder import android.os.IBinder import android.telephony.PhoneStateListener import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyManager 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 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. 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" } // Define the class variables. private var currentStatus = "" private lateinit var phoneStateListener: PhoneStateListener inner class ServiceBinder : Binder() { // Get a copy of this service as a binder. fun getService(): RealtimeMonitoringService = this@RealtimeMonitoringService } 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 // Create a notification channel group. 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_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. secureNetworkChannel.group = REALTIME_MONITORING insecureNetworkChannel.group = REALTIME_MONITORING unknownNetworkChannel.group = REALTIME_MONITORING // Disable the notification dots. secureNetworkChannel.setShowBadge(false) insecureNetworkChannel.setShowBadge(false) unknownNetworkChannel.setShowBadge(false) // Set the notifications to be public for the secure and insecure networks. secureNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC insecureNetworkChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC // Create the notification channels. notificationManager.createNotificationChannel(secureNetworkChannel) notificationManager.createNotificationChannel(insecureNetworkChannel) notificationManager.createNotificationChannel(unknownNetworkChannel) // Create a notification builder. val notificationBuilder = Notification.Builder(this, UNKNOWN_NETWORK) // 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) // Set the notification to open Privacy Cell. notificationBuilder.setContentIntent(privacyCellPendingIntent) // Set the notification text. notificationBuilder.setContentText(getString(R.string.unknown_network)) // Set the notification icon. notificationBuilder.setSmallIcon(R.drawable.insecure_notification) // Set the color. notificationBuilder.setColor(getColor(R.color.red_text)) // Start the foreground notification. startForeground(NOTIFICATION_ID, notificationBuilder.build()) // Define the phone state listener. 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. // Only update the notification if the network status has changed. if (currentStatus != SECURE_NETWORK) { // Create a secure network notification builder. val secureNetworkNotificationBuilder = Notification.Builder(applicationContext, SECURE_NETWORK) // Set the notification to open Privacy Cell. secureNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent) // Set the notification text. secureNetworkNotificationBuilder.setContentText(getString(R.string.secure_network)) // Set the notification icon. secureNetworkNotificationBuilder.setSmallIcon(R.drawable.secure_notification) // Set the color. secureNetworkNotificationBuilder.setColor(getColor(R.color.blue_text)) // 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. // Only update the notification if the network status has changed. if (currentStatus != INSECURE_NETWORK) { // Create an insecure network notification builder. val insecureNetworkNotificationBuilder = Notification.Builder(applicationContext, INSECURE_NETWORK) // Set the notification to open Privacy Cell. insecureNetworkNotificationBuilder.setContentIntent(privacyCellPendingIntent) // Set the notification text. insecureNetworkNotificationBuilder.setContentText(getString(R.string.insecure_network)) // Set the notification icon. insecureNetworkNotificationBuilder.setSmallIcon(R.drawable.insecure_notification) // Set the color. insecureNetworkNotificationBuilder.setColor(getColor(R.color.red_text)) // Update the notification. notificationManager.notify(NOTIFICATION_ID, insecureNetworkNotificationBuilder.build()) // Store the new network status. currentStatus = INSECURE_NETWORK } } } } // Create a register realtime listener work request that fires every hour. For some reason, when the service launches it will initially register the listener and then unregister it. // This periodic request will fire shortly thereafter (it fires about every hour near the beginning of the hour) and will reregister the listener, which will stick this time. val registerRealtimeListenerWorkRequest = PeriodicWorkRequestBuilder(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() { // Get a handle for the telephony manager. val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager // Cancel the current listener if it exists. telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE) // Listen for changes to the phone state. telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) } }