1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 @file:JvmName("VpnStatusObserver")
18 
19 package com.android.systemui.tv.vpn
20 
21 import android.app.Notification
22 import android.app.NotificationChannel
23 import android.app.NotificationManager
24 import android.content.Context
25 import com.android.internal.messages.nano.SystemMessageProto
26 import com.android.internal.net.VpnConfig
27 import com.android.systemui.CoreStartable
28 import com.android.systemui.dagger.SysUISingleton
29 import com.android.systemui.statusbar.policy.SecurityController
30 import com.android.systemui.tv.res.R
31 import javax.inject.Inject
32 
33 /**
34  * Observes if a vpn connection is active and displays a notification to the user
35  */
36 @SysUISingleton
37 class VpnStatusObserver @Inject constructor(
38     private val context: Context,
39     private val securityController: SecurityController
40 ) : CoreStartable,
41         SecurityController.SecurityControllerCallback {
42 
43     private var vpnConnected = false
44     private val notificationManager = NotificationManager.from(context)
45     private val notificationChannel = createNotificationChannel()
46     private val vpnConnectedNotificationBuilder = createVpnConnectedNotificationBuilder()
47     private val vpnDisconnectedNotification = createVpnDisconnectedNotification()
48 
49     private val vpnIconId: Int
50         get() = if (securityController.isVpnBranded) {
51             com.android.systemui.res.R.drawable.stat_sys_branded_vpn
52         } else {
53             com.android.systemui.res.R.drawable.stat_sys_vpn_ic
54         }
55 
56     private val vpnName: String?
57         get() = securityController.primaryVpnName ?: securityController.workProfileVpnName
58 
59     override fun start() {
60         // register callback to vpn state changes
61         securityController.addCallback(this)
62     }
63 
64     override fun onStateChanged() {
65         securityController.isVpnEnabled.let { newVpnConnected ->
66             if (vpnConnected != newVpnConnected) {
67                 if (newVpnConnected) {
68                     notifyVpnConnected()
69                 } else {
70                     notifyVpnDisconnected()
71                 }
72                 vpnConnected = newVpnConnected
73             }
74         }
75     }
76 
77     private fun notifyVpnConnected() = notificationManager.notify(
78             NOTIFICATION_TAG,
79             SystemMessageProto.SystemMessage.NOTE_VPN_STATUS,
80             createVpnConnectedNotification()
81     )
82 
83     private fun notifyVpnDisconnected() = notificationManager.run {
84         // remove existing connected notification
85         cancel(NOTIFICATION_TAG, SystemMessageProto.SystemMessage.NOTE_VPN_STATUS)
86         // show the disconnected notification only for a short while
87         notify(
88             NOTIFICATION_TAG,
89             SystemMessageProto.SystemMessage.NOTE_VPN_DISCONNECTED,
90                 vpnDisconnectedNotification
91         )
92     }
93 
94     private fun createNotificationChannel() =
95             NotificationChannel(
96                     NOTIFICATION_CHANNEL_TV_VPN,
97                     NOTIFICATION_CHANNEL_TV_VPN,
98                     NotificationManager.IMPORTANCE_HIGH
99             ).also {
100                 notificationManager.createNotificationChannel(it)
101             }
102 
103     private fun createVpnConnectedNotification() =
104             vpnConnectedNotificationBuilder.apply {
105                 vpnName?.let {
106                             setContentText(
107                                 context.getString(R.string.notification_disclosure_vpn_text, it)
108                             )
109                     }
110                 }.build()
111 
112     private fun createVpnConnectedNotificationBuilder() =
113             Notification.Builder(context, NOTIFICATION_CHANNEL_TV_VPN)
114                     .setSmallIcon(vpnIconId)
115                     .setVisibility(Notification.VISIBILITY_PUBLIC)
116                     .setCategory(Notification.CATEGORY_SYSTEM)
117                     .extend(Notification.TvExtender())
118                     .setOngoing(true)
119                     .setContentTitle(context.getString(R.string.notification_vpn_connected))
120                     .setContentIntent(VpnConfig.getIntentForStatusPanel(context))
121 
122     private fun createVpnDisconnectedNotification() =
123             Notification.Builder(context, NOTIFICATION_CHANNEL_TV_VPN)
124                     .setSmallIcon(vpnIconId)
125                     .setVisibility(Notification.VISIBILITY_PUBLIC)
126                     .setCategory(Notification.CATEGORY_SYSTEM)
127                     .extend(Notification.TvExtender())
128                     .setTimeoutAfter(VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS)
129                     .setContentTitle(context.getString(R.string.notification_vpn_disconnected))
130                     .build()
131 
132     companion object {
133         const val NOTIFICATION_CHANNEL_TV_VPN = "VPN Status"
134         val NOTIFICATION_TAG: String = VpnStatusObserver::class.java.simpleName
135 
136         private const val TAG = "TvVpnNotification"
137         private const val VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS = 5_000L
138     }
139 }
140