From 3382392f9eb69de3072fd0ae0c3808adea2e1fc9 Mon Sep 17 00:00:00 2001 From: Dorin Drimus Date: Fri, 23 Oct 2020 13:35:56 +0200 Subject: [PATCH] Notify VPN status from SystemUI as notifications A `VPN is connected.` ongoing notification is sent when connected to a VPN and a `VPN is disconnected.` short lived notification is send when disconnected from a VPN. Test: Manual tests. Connecting and disconnecting to VPNs (from a VPN client app) show the (undismissable, ongoing) connected and (dismissable, short lived) disconnected notifications. A CTS test will be added in a separate CL. Bug: 157500504 Change-Id: I321dec45127104b5139b9e868bac76b9d882e9f2 --- .../SystemUI/res/values-television/config.xml | 1 + packages/SystemUI/res/values/strings_tv.xml | 6 + .../statusbar/tv/VpnStatusObserver.kt | 140 ++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml index 015ac90e2521c..cebe1e83ceeda 100644 --- a/packages/SystemUI/res/values-television/config.xml +++ b/packages/SystemUI/res/values-television/config.xml @@ -30,6 +30,7 @@ com.android.systemui.volume.VolumeUI com.android.systemui.statusbar.tv.TvStatusBar com.android.systemui.statusbar.tv.TvNotificationPanel + com.android.systemui.statusbar.tv.VpnStatusObserver com.android.systemui.usb.StorageNotification com.android.systemui.power.PowerUI com.android.systemui.media.RingtonePlayer diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml index 5d7f08eef0a10..13271d6f7f2e3 100644 --- a/packages/SystemUI/res/values/strings_tv.xml +++ b/packages/SystemUI/res/values/strings_tv.xml @@ -20,4 +20,10 @@ Microphone Active %1$s accessed your microphone + + VPN is connected + VPN is disconnected + + Via %1$s + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt new file mode 100644 index 0000000000000..7c6f86aceeb0d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:JvmName("VpnStatusObserver") + +package com.android.systemui.statusbar.tv + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import com.android.internal.messages.nano.SystemMessageProto +import com.android.internal.net.VpnConfig +import com.android.systemui.Dependency +import com.android.systemui.R +import com.android.systemui.SystemUI +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.policy.SecurityController +import javax.inject.Inject + +/** + * Observes if a vpn connection is active and displays a notification to the user + */ +@SysUISingleton +class VpnStatusObserver @Inject constructor(context: Context) : SystemUI(context), + SecurityController.SecurityControllerCallback { + + private var vpnConnected = false + private val securityController: SecurityController = + Dependency.get(SecurityController::class.java) + private val notificationManager = NotificationManager.from(context) + private val notificationChannel = createNotificationChannel() + private val vpnConnectedNotificationBuilder = createVpnConnectedNotificationBuilder() + private val vpnDisconnectedNotification = createVpnDisconnectedNotification() + + private val vpnIconId: Int + get() = if (securityController.isVpnBranded) { + R.drawable.stat_sys_branded_vpn + } else { + R.drawable.stat_sys_vpn_ic + } + + private val vpnName: String? + get() = securityController.primaryVpnName ?: securityController.workProfileVpnName + + override fun start() { + // register callback to vpn state changes + securityController.addCallback(this) + } + + override fun onStateChanged() { + securityController.isVpnEnabled.let { newVpnConnected -> + if (vpnConnected != newVpnConnected) { + if (newVpnConnected) { + notifyVpnConnected() + } else { + notifyVpnDisconnected() + } + vpnConnected = newVpnConnected + } + } + } + + private fun notifyVpnConnected() = notificationManager.notify( + NOTIFICATION_TAG, + SystemMessageProto.SystemMessage.NOTE_VPN_STATUS, + createVpnConnectedNotification() + ) + + private fun notifyVpnDisconnected() = notificationManager.run { + // remove existing connected notification + cancel(NOTIFICATION_TAG, SystemMessageProto.SystemMessage.NOTE_VPN_STATUS) + // show the disconnected notification only for a short while + notify(NOTIFICATION_TAG, SystemMessageProto.SystemMessage.NOTE_VPN_DISCONNECTED, + vpnDisconnectedNotification) + } + + private fun createNotificationChannel() = + NotificationChannel( + NOTIFICATION_CHANNEL_TV_VPN, + NOTIFICATION_CHANNEL_TV_VPN, + NotificationManager.IMPORTANCE_HIGH + ).also { + notificationManager.createNotificationChannel(it) + } + + private fun createVpnConnectedNotification() = + vpnConnectedNotificationBuilder + .apply { + vpnName?.let { + setContentText( + mContext.getString( + R.string.notification_disclosure_vpn_text, it + ) + ) + } + } + .build() + + private fun createVpnConnectedNotificationBuilder() = + Notification.Builder(mContext, NOTIFICATION_CHANNEL_TV_VPN) + .setSmallIcon(vpnIconId) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setCategory(Notification.CATEGORY_SYSTEM) + .extend(Notification.TvExtender()) + .setOngoing(true) + .setContentTitle(mContext.getString(R.string.notification_vpn_connected)) + .setContentIntent(VpnConfig.getIntentForStatusPanel(mContext)) + + private fun createVpnDisconnectedNotification() = + Notification.Builder(mContext, NOTIFICATION_CHANNEL_TV_VPN) + .setSmallIcon(vpnIconId) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setCategory(Notification.CATEGORY_SYSTEM) + .extend(Notification.TvExtender()) + .setTimeoutAfter(VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS) + .setContentTitle(mContext.getString(R.string.notification_vpn_disconnected)) + .build() + + companion object { + const val NOTIFICATION_CHANNEL_TV_VPN = "VPN Status" + val NOTIFICATION_TAG: String = VpnStatusObserver::class.java.simpleName + + private const val TAG = "TvVpnNotification" + private const val VPN_DISCONNECTED_NOTIFICATION_TIMEOUT_MS = 5_000L + } +} \ No newline at end of file