1 /* 2 * Copyright (C) 2020 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.statusbar.tv 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.R 29 import com.android.systemui.dagger.SysUISingleton 30 import com.android.systemui.statusbar.policy.SecurityController 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 R.drawable.stat_sys_branded_vpn 52 } else { 53 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(NOTIFICATION_TAG, SystemMessageProto.SystemMessage.NOTE_VPN_DISCONNECTED, 88 vpnDisconnectedNotification) 89 } 90 91 private fun createNotificationChannel() = 92 NotificationChannel( 93 NOTIFICATION_CHANNEL_TV_VPN, 94 NOTIFICATION_CHANNEL_TV_VPN, 95 NotificationManager.IMPORTANCE_HIGH 96 ).also { 97 notificationManager.createNotificationChannel(it) 98 } 99 100 private fun createVpnConnectedNotification() = 101 vpnConnectedNotificationBuilder 102 .apply { 103 vpnName?.let { 104 setContentText( 105 context.getString( 106 R.string.notification_disclosure_vpn_text, it 107 ) 108 ) 109 } 110 } 111 .build() 112 113 private fun createVpnConnectedNotificationBuilder() = 114 Notification.Builder(context, NOTIFICATION_CHANNEL_TV_VPN) 115 .setSmallIcon(vpnIconId) 116 .setVisibility(Notification.VISIBILITY_PUBLIC) 117 .setCategory(Notification.CATEGORY_SYSTEM) 118 .extend(Notification.TvExtender()) 119 .setOngoing(true) 120 .setContentTitle(context.getString(R.string.notification_vpn_connected)) 121 .setContentIntent(VpnConfig.getIntentForStatusPanel(context)) 122 123 private fun createVpnDisconnectedNotification() = 124 Notification.Builder(context, NOTIFICATION_CHANNEL_TV_VPN) 125 .setSmallIcon(vpnIconId) 126 .setVisibility(Notification.VISIBILITY_PUBLIC) 127 .setCategory(Notification.CATEGORY_SYSTEM) 128 .extend(Notification.TvExtender()) 129 .setTimeoutAfter(VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS) 130 .setContentTitle(context.getString(R.string.notification_vpn_disconnected)) 131 .build() 132 133 companion object { 134 const val NOTIFICATION_CHANNEL_TV_VPN = "VPN Status" 135 val NOTIFICATION_TAG: String = VpnStatusObserver::class.java.simpleName 136 137 private const val TAG = "TvVpnNotification" 138 private const val VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS = 5_000L 139 } 140 } 141