From b0bde8b0a71f0428a9931a90b6244e49ec340cc4 Mon Sep 17 00:00:00 2001 From: Jason Elie Bou Kheir <5115126+jasonboukheir@users.noreply.github.com> Date: Sat, 30 Mar 2024 11:05:57 -0700 Subject: [PATCH] feat(android): UI notification for reauth (#3621) Fixes #3329 --------- Signed-off-by: Jason Elie Bou Kheir <5115126+jasonboukheir@users.noreply.github.com> Signed-off-by: Jamil Co-authored-by: Jamil --- .../firezone/android/tunnel/NetworkMonitor.kt | 3 +- .../firezone/android/tunnel/TunnelService.kt | 51 ++------- .../tunnel/TunnelStatusNotification.kt | 100 ++++++++++++++++++ 3 files changed, 109 insertions(+), 45 deletions(-) create mode 100644 kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelStatusNotification.kt diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/NetworkMonitor.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/NetworkMonitor.kt index 2b29fb222..906167f41 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/NetworkMonitor.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/NetworkMonitor.kt @@ -6,6 +6,7 @@ import android.util.Log import com.google.gson.Gson import dev.firezone.android.tunnel.ConnlibSession import dev.firezone.android.tunnel.TunnelService +import dev.firezone.android.tunnel.TunnelStatusNotification import java.net.InetAddress class NetworkMonitor(private val tunnelService: TunnelService) : ConnectivityManager.NetworkCallback() { @@ -19,7 +20,7 @@ class NetworkMonitor(private val tunnelService: TunnelService) : ConnectivityMan Log.d("NetworkMonitor", "OnLinkPropertiesChanged: $network: $linkProperties") if (tunnelService.tunnelState != TunnelService.Companion.State.UP) { tunnelService.tunnelState = TunnelService.Companion.State.UP - tunnelService.updateStatusNotification("Status: Connected") + tunnelService.updateStatusNotification(TunnelStatusNotification.Connected) } if (lastDns != linkProperties.dnsServers) { diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelService.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelService.kt index 4ecead419..29d34d7be 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelService.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelService.kt @@ -3,10 +3,6 @@ package dev.firezone.android.tunnel import NetworkMonitor import android.app.ActivityManager -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -20,16 +16,13 @@ import android.os.Build import android.os.Bundle import android.os.IBinder import android.util.Log -import androidx.core.app.NotificationCompat import androidx.lifecycle.MutableLiveData import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase import com.squareup.moshi.Moshi import com.squareup.moshi.adapter import dagger.hilt.android.AndroidEntryPoint -import dev.firezone.android.R import dev.firezone.android.core.data.Repository -import dev.firezone.android.core.presentation.MainActivity import dev.firezone.android.tunnel.callback.ConnlibCallback import dev.firezone.android.tunnel.model.Cidr import dev.firezone.android.tunnel.model.Resource @@ -143,7 +136,9 @@ class TunnelService : VpnService() { repo.clearActorName() shutdown() - + if (startedByUser) { + updateStatusNotification(TunnelStatusNotification.SignedOut) + } return true } @@ -232,7 +227,7 @@ class TunnelService : VpnService() { if (!token.isNullOrBlank()) { tunnelState = State.CONNECTING - updateStatusNotification("Status: Connecting...") + updateStatusNotification(TunnelStatusNotification.Connecting) System.loadLibrary("connlib") connlibSessionPtr = @@ -307,15 +302,6 @@ class TunnelService : VpnService() { return deviceId } - private fun configIntent(): PendingIntent? { - return PendingIntent.getActivity( - this, - 0, - Intent(this, MainActivity::class.java), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, - ) - } - private fun getLogDir(): String { // Create log directory if it doesn't exist val logDir = cacheDir.absolutePath + "/logs" @@ -388,27 +374,9 @@ class TunnelService : VpnService() { } } - fun updateStatusNotification(message: String?) { - val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager - - val chan = - NotificationChannel( - NOTIFICATION_CHANNEL_ID, - NOTIFICATION_CHANNEL_NAME, - NotificationManager.IMPORTANCE_DEFAULT, - ) - chan.description = "Firezone connection status" - - manager.createNotificationChannel(chan) - - val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) - val notification = - notificationBuilder.setOngoing(true).setSmallIcon(R.drawable.ic_firezone_logo) - .setContentTitle(NOTIFICATION_TITLE).setContentText(message) - .setPriority(NotificationManager.IMPORTANCE_MIN) - .setCategory(Notification.CATEGORY_SERVICE).setContentIntent(configIntent()).build() - - startForeground(STATUS_NOTIFICATION_ID, notification) + fun updateStatusNotification(statusType: TunnelStatusNotification.StatusType) { + val notification = TunnelStatusNotification.update(this, statusType).build() + startForeground(TunnelStatusNotification.ID, notification) } private fun getDeviceName(): String { @@ -427,11 +395,6 @@ class TunnelService : VpnService() { DOWN, } - private const val NOTIFICATION_CHANNEL_ID = "firezone-connection-status" - private const val NOTIFICATION_CHANNEL_NAME = "firezone-connection-status" - private const val STATUS_NOTIFICATION_ID = 1337 - private const val NOTIFICATION_TITLE = "Firezone Connection Status" - private const val TAG: String = "TunnelService" private const val SESSION_NAME: String = "Firezone Connection" private const val MTU: Int = 1280 diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelStatusNotification.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelStatusNotification.kt new file mode 100644 index 000000000..d41571bd7 --- /dev/null +++ b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelStatusNotification.kt @@ -0,0 +1,100 @@ +/* Licensed under Apache 2.0 (C) 2024 Firezone, Inc. */ +package dev.firezone.android.tunnel + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Context.NOTIFICATION_SERVICE +import android.content.Intent +import android.util.Log +import androidx.core.app.NotificationCompat +import dev.firezone.android.R +import dev.firezone.android.core.presentation.MainActivity + +object TunnelStatusNotification { + private const val CHANNEL_ID = "firezone-connection-status" + private const val CHANNEL_NAME = "firezone-connection-status" + private const val CHANNEL_DESCRIPTION = "Firezone connection status" + const val ID = 1337 + private const val TITLE = "Firezone Connection Status" + private const val TAG: String = "TunnelStatusNotification" + + fun update( + context: Context, + status: StatusType, + ): NotificationCompat.Builder { + Log.d(TAG, "update") + val manager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager + + val chan = + NotificationChannel( + CHANNEL_ID, + CHANNEL_NAME, + NotificationManager.IMPORTANCE_DEFAULT, + ) + chan.description = CHANNEL_DESCRIPTION + manager.createNotificationChannel(chan) + + val notificationBuilder = + NotificationCompat.Builder(context, CHANNEL_ID) + .setContentIntent(configIntent(context)) + return status.applySettings(notificationBuilder) + } + + private fun configIntent(context: Context): PendingIntent { + Log.d(TAG, "configIntent") + val intent = Intent(context, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + return PendingIntent.getActivity( + context, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) + } + + data object SignedOut : StatusType() { + private const val MESSAGE = "Status: Signed out. Tap here to sign in." + + override fun applySettings(builder: NotificationCompat.Builder) = + builder + .setSmallIcon(R.drawable.ic_firezone_logo) + .setContentTitle(TITLE) + .setContentText(MESSAGE) + .setCategory(NotificationCompat.CATEGORY_ERROR) + .setPriority(NotificationManager.IMPORTANCE_HIGH) + .setAutoCancel(true) + } + + data object Connecting : StatusType() { + private const val MESSAGE = "Status: Connecting..." + + override fun applySettings(builder: NotificationCompat.Builder) = + builder + .setSmallIcon(R.drawable.ic_firezone_logo) + .setContentTitle(TITLE) + .setContentText(MESSAGE) + .setCategory(Notification.CATEGORY_SERVICE) + .setPriority(NotificationManager.IMPORTANCE_MIN) + .setOngoing(true) + } + + data object Connected : StatusType() { + private const val MESSAGE = "Status: Connected" + + override fun applySettings(builder: NotificationCompat.Builder) = + builder + .setSmallIcon(R.drawable.ic_firezone_logo) + .setContentTitle(TITLE) + .setContentText(MESSAGE) + .setCategory(Notification.CATEGORY_SERVICE) + .setPriority(NotificationManager.IMPORTANCE_MIN) + .setOngoing(true) + } + + sealed class StatusType { + abstract fun applySettings(builder: NotificationCompat.Builder): NotificationCompat.Builder + } +}