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