diff options
7 files changed, 178 insertions, 3 deletions
diff --git a/packages/SystemUI/res/drawable/stat_sys_no_internet_branded_vpn.xml b/packages/SystemUI/res/drawable/stat_sys_no_internet_branded_vpn.xml new file mode 100644 index 000000000000..8122d74ca229 --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_sys_no_internet_branded_vpn.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2023, 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. +*/ +--> +<shape /> diff --git a/packages/SystemUI/res/drawable/stat_sys_no_internet_vpn_ic.xml b/packages/SystemUI/res/drawable/stat_sys_no_internet_vpn_ic.xml new file mode 100644 index 000000000000..8122d74ca229 --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_sys_no_internet_vpn_ic.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2023, 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. +*/ +--> +<shape /> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java index d731f8886536..bd1e76851c62 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java @@ -127,15 +127,25 @@ public class StatusBarSignalPolicy implements SignalCallback, private void updateVpn() { boolean vpnVisible = mSecurityController.isVpnEnabled(); - int vpnIconId = currentVpnIconId(mSecurityController.isVpnBranded()); + int vpnIconId = currentVpnIconId( + mSecurityController.isVpnBranded(), + mSecurityController.isVpnValidated()); mIconController.setIcon(mSlotVpn, vpnIconId, mContext.getResources().getString(R.string.accessibility_vpn_on)); mIconController.setIconVisibility(mSlotVpn, vpnVisible); } - private int currentVpnIconId(boolean isBranded) { - return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic; + private int currentVpnIconId(boolean isBranded, boolean isValidated) { + if (isBranded) { + return isValidated + ? R.drawable.stat_sys_branded_vpn + : R.drawable.stat_sys_no_internet_branded_vpn; + } else { + return isValidated + ? R.drawable.stat_sys_vpn_ic + : R.drawable.stat_sys_no_internet_vpn_ic; + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java index 3be14bc867a1..10bf0680b567 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java @@ -48,6 +48,8 @@ public interface SecurityController extends CallbackController<SecurityControlle boolean isNetworkLoggingEnabled(); boolean isVpnEnabled(); boolean isVpnRestricted(); + /** Whether the VPN network is validated. */ + boolean isVpnValidated(); /** Whether the VPN app should use branded VPN iconography. */ boolean isVpnBranded(); String getPrimaryVpnName(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index 03656f000c07..55bbd79c951b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -15,6 +15,8 @@ */ package com.android.systemui.statusbar.policy; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; + import android.annotation.Nullable; import android.app.admin.DeviceAdminInfo; import android.app.admin.DevicePolicyManager; @@ -32,7 +34,9 @@ import android.content.pm.UserInfo; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; +import android.net.LinkProperties; import android.net.Network; +import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.VpnManager; import android.os.Handler; @@ -99,6 +103,8 @@ public class SecurityControllerImpl implements SecurityController { private SparseArray<VpnConfig> mCurrentVpns = new SparseArray<>(); private int mCurrentUserId; private int mVpnUserId; + @GuardedBy("mNetworkProperties") + private final SparseArray<NetworkProperties> mNetworkProperties = new SparseArray<>(); // Key: userId, Value: whether the user has CACerts installed // Needs to be cached here since the query has to be asynchronous @@ -162,6 +168,21 @@ public class SecurityControllerImpl implements SecurityController { pw.print(mCurrentVpns.valueAt(i).user); } pw.println("}"); + pw.print(" mNetworkProperties={"); + synchronized (mNetworkProperties) { + for (int i = 0; i < mNetworkProperties.size(); ++i) { + if (i > 0) { + pw.print(", "); + } + pw.print(mNetworkProperties.keyAt(i)); + pw.print("={"); + pw.print(mNetworkProperties.valueAt(i).interfaceName); + pw.print(", "); + pw.print(mNetworkProperties.valueAt(i).validated); + pw.print("}"); + } + } + pw.println("}"); } @Override @@ -304,6 +325,26 @@ public class SecurityControllerImpl implements SecurityController { } @Override + public boolean isVpnValidated() { + // Prioritize reporting the network status of the parent user. + final VpnConfig primaryVpnConfig = mCurrentVpns.get(mVpnUserId); + if (primaryVpnConfig != null) { + return getVpnValidationStatus(primaryVpnConfig); + } + // Identify any Unvalidated status in each active VPN network within other profiles. + for (int profileId : mUserManager.getEnabledProfileIds(mVpnUserId)) { + final VpnConfig vpnConfig = mCurrentVpns.get(profileId); + if (vpnConfig == null) { + continue; + } + if (!getVpnValidationStatus(vpnConfig)) { + return false; + } + } + return true; + } + + @Override public boolean hasCACertInCurrentUser() { Boolean hasCACerts = mHasCACerts.get(mCurrentUserId); return hasCACerts != null && hasCACerts.booleanValue(); @@ -491,11 +532,74 @@ public class SecurityControllerImpl implements SecurityController { @Override public void onLost(Network network) { if (DEBUG) Log.d(TAG, "onLost " + network.getNetId()); + synchronized (mNetworkProperties) { + mNetworkProperties.delete(network.getNetId()); + } updateState(); fireCallbacks(); }; + + + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { + if (DEBUG) Log.d(TAG, "onCapabilitiesChanged " + network.getNetId()); + final NetworkProperties properties; + synchronized (mNetworkProperties) { + properties = mNetworkProperties.get(network.getNetId()); + } + // When a new network appears, the system first notifies the application about + // its capabilities through onCapabilitiesChanged. This initial notification + // will be skipped because the interface information is included in the + // subsequent onLinkPropertiesChanged call. After validating the network, the + // system might send another onCapabilitiesChanged notification if the network + // becomes validated. + if (properties == null) { + return; + } + final boolean validated = nc.hasCapability(NET_CAPABILITY_VALIDATED); + if (properties.validated != validated) { + properties.validated = validated; + fireCallbacks(); + } + } + + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { + if (DEBUG) Log.d(TAG, "onLinkPropertiesChanged " + network.getNetId()); + final String interfaceName = linkProperties.getInterfaceName(); + if (interfaceName == null) { + Log.w(TAG, "onLinkPropertiesChanged event with null interface"); + return; + } + synchronized (mNetworkProperties) { + final NetworkProperties properties = mNetworkProperties.get(network.getNetId()); + if (properties == null) { + mNetworkProperties.put( + network.getNetId(), + new NetworkProperties(interfaceName, false)); + } else { + properties.interfaceName = interfaceName; + } + } + } }; + /** + * Retrieve the validation status of the VPN network associated with the given VpnConfig. + */ + private boolean getVpnValidationStatus(@NonNull VpnConfig vpnConfig) { + synchronized (mNetworkProperties) { + // Find the network has the same interface as the VpnConfig + for (int i = 0; i < mNetworkProperties.size(); ++i) { + if (mNetworkProperties.valueAt(i).interfaceName.equals(vpnConfig.interfaze)) { + return mNetworkProperties.valueAt(i).validated; + } + } + } + // If no matching network is found, consider it validated. + return true; + } + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) { @@ -506,4 +610,17 @@ public class SecurityControllerImpl implements SecurityController { } } }; + + /** + * A data class to hold specific Network properties received through the NetworkCallback. + */ + private static class NetworkProperties { + public String interfaceName; + public boolean validated; + + NetworkProperties(@NonNull String interfaceName, boolean validated) { + this.interfaceName = interfaceName; + this.validated = validated; + } + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt index 021e7dff9120..ac90a45450d0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt @@ -77,6 +77,8 @@ class FakeSecurityController( override fun isVpnBranded(): Boolean = fakeState.isVpnBranded + override fun isVpnValidated(): Boolean = fakeState.isVpnValidated + override fun getPrimaryVpnName(): String? = fakeState.primaryVpnName override fun getWorkProfileVpnName(): String? = fakeState.workProfileVpnName @@ -110,6 +112,7 @@ class FakeSecurityController( var isVpnEnabled: Boolean = false, var isVpnRestricted: Boolean = false, var isVpnBranded: Boolean = false, + var isVpnValidated: Boolean = false, var primaryVpnName: String? = null, var workProfileVpnName: String? = null, var hasCACertInCurrentUser: Boolean = false, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java index 76199e3168da..791165d97795 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java @@ -109,6 +109,11 @@ public class FakeSecurityController extends BaseLeakChecker<SecurityControllerCa } @Override + public boolean isVpnValidated() { + return false; + } + + @Override public String getPrimaryVpnName() { return null; } |