diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 1fbfa40d5d..4714587449 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2816,10 +2816,11 @@ public class ConnectivityManager { * @param network The {@link Network} of the satisfying network. * @param networkCapabilities The {@link NetworkCapabilities} of the satisfying network. * @param linkProperties The {@link LinkProperties} of the satisfying network. + * @param blocked Whether access to the {@link Network} is blocked due to system policy. * @hide */ public void onAvailable(Network network, NetworkCapabilities networkCapabilities, - LinkProperties linkProperties) { + LinkProperties linkProperties, boolean blocked) { // Internally only this method is called when a new network is available, and // it calls the callback in the same way and order that older versions used // to call so as not to change the behavior. @@ -2830,6 +2831,7 @@ public class ConnectivityManager { } onCapabilitiesChanged(network, networkCapabilities); onLinkPropertiesChanged(network, linkProperties); + onBlockedStatusChanged(network, blocked); } /** @@ -2837,7 +2839,8 @@ public class ConnectivityManager { * This callback may be called more than once if the {@link Network} that is * satisfying the request changes. This will always immediately be followed by a * call to {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} then by a - * call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}. + * call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}, and a call to + * {@link #onBlockedStatusChanged(Network, boolean)}. * * @param network The {@link Network} of the satisfying network. */ @@ -2916,6 +2919,14 @@ public class ConnectivityManager { */ public void onNetworkResumed(Network network) {} + /** + * Called when access to the specified network is blocked or unblocked. + * + * @param network The {@link Network} whose blocked status has changed. + * @param blocked The blocked status of this {@link Network}. + */ + public void onBlockedStatusChanged(Network network, boolean blocked) {} + private NetworkRequest networkRequest; } @@ -2962,6 +2973,8 @@ public class ConnectivityManager { public static final int CALLBACK_SUSPENDED = BASE + 9; /** @hide */ public static final int CALLBACK_RESUMED = BASE + 10; + /** @hide */ + public static final int CALLBACK_BLK_CHANGED = BASE + 11; /** @hide */ public static String getCallbackName(int whichCallback) { @@ -2976,6 +2989,7 @@ public class ConnectivityManager { case EXPIRE_LEGACY_REQUEST: return "EXPIRE_LEGACY_REQUEST"; case CALLBACK_SUSPENDED: return "CALLBACK_SUSPENDED"; case CALLBACK_RESUMED: return "CALLBACK_RESUMED"; + case CALLBACK_BLK_CHANGED: return "CALLBACK_BLK_CHANGED"; default: return Integer.toString(whichCallback); } @@ -3022,7 +3036,7 @@ public class ConnectivityManager { case CALLBACK_AVAILABLE: { NetworkCapabilities cap = getObject(message, NetworkCapabilities.class); LinkProperties lp = getObject(message, LinkProperties.class); - callback.onAvailable(network, cap, lp); + callback.onAvailable(network, cap, lp, message.arg1 != 0); break; } case CALLBACK_LOSING: { @@ -3055,6 +3069,10 @@ public class ConnectivityManager { callback.onNetworkResumed(network); break; } + case CALLBACK_BLK_CHANGED: { + boolean blocked = message.arg1 != 0; + callback.onBlockedStatusChanged(network, blocked); + } } } diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index fd1e5f2338..74cd1433f5 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -1589,4 +1589,14 @@ public final class NetworkCapabilities implements Parcelable { Preconditions.checkArgument(isValidCapability(capability), "NetworkCapability " + capability + "out of range"); } + + /** + * Check if this {@code NetworkCapability} instance is metered. + * + * @return {@code true} if {@code NET_CAPABILITY_NOT_METERED} is not set on this instance. + * @hide + */ + public boolean isMetered() { + return !hasCapability(NET_CAPABILITY_NOT_METERED); + } } diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index d912dd105f..1a1d2d3342 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -202,7 +202,9 @@ public class NetworkInfo implements Parcelable { * Return a network-type-specific integer describing the subtype * of the network. * @return the network subtype + * @deprecated Use {@link android.telephony.TelephonyManager#getDataNetworkType} instead. */ + @Deprecated public int getSubtype() { synchronized (this) { return mSubtype; @@ -243,7 +245,9 @@ public class NetworkInfo implements Parcelable { /** * Return a human-readable name describing the subtype of the network. * @return the name of the network subtype + * @deprecated Use {@link android.telephony.TelephonyManager#getDataNetworkType} instead. */ + @Deprecated public String getSubtypeName() { synchronized (this) { return mSubtypeName; @@ -278,7 +282,15 @@ public class NetworkInfo implements Parcelable { * connections and pass data. *

Always call this before attempting to perform data transactions. * @return {@code true} if network connectivity exists, {@code false} otherwise. + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. See + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. */ + @Deprecated public boolean isConnected() { synchronized (this) { return mState == State.CONNECTED; @@ -411,7 +423,15 @@ public class NetworkInfo implements Parcelable { /** * Reports the current fine-grained state of the network. * @return the fine-grained state + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. See + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. */ + @Deprecated public DetailedState getDetailedState() { synchronized (this) { return mDetailedState; diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index b750d79591..4ce9016744 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -35,6 +35,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkPolicyManager.RULE_NONE; +import static android.net.NetworkPolicyManager.uidRulesToString; import static android.os.Process.INVALID_UID; import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; @@ -189,6 +191,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; +import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -257,6 +260,14 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mVpns") private LockdownVpnTracker mLockdownTracker; + /** + * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal + * handler thread, they don't need a lock. + */ + private SparseIntArray mUidRules = new SparseIntArray(); + /** Flag indicating if background data is restricted. */ + private boolean mRestrictBackground; + final private Context mContext; // 0 is full bad, 100 is full good private int mDefaultInetConditionPublished = 0; @@ -419,6 +430,16 @@ public class ConnectivityService extends IConnectivityManager.Stub // Handle private DNS validation status updates. private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38; + /** + * Used to handle onUidRulesChanged event from NetworkPolicyManagerService. + */ + private static final int EVENT_UID_RULES_CHANGED = 39; + + /** + * Used to handle onRestrictBackgroundChanged event from NetworkPolicyManagerService. + */ + private static final int EVENT_DATA_SAVER_CHANGED = 40; + private static String eventName(int what) { return sMagicDecoderRing.get(what, Integer.toString(what)); } @@ -780,6 +801,9 @@ public class ConnectivityService extends IConnectivityManager.Stub mKeyStore = KeyStore.getInstance(); mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + // To ensure uid rules are synchronized with Network Policy, register for + // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService + // reading existing policy from disk. try { mPolicyManager.registerListener(mPolicyListener); } catch (RemoteException e) { @@ -1116,11 +1140,6 @@ public class ConnectivityService extends IConnectivityManager.Stub if (ignoreBlocked) { return false; } - // Networks are never blocked for system services - // TODO: consider moving this check to NetworkPolicyManagerInternal.isUidNetworkingBlocked. - if (isSystem(uid)) { - return false; - } synchronized (mVpns) { final Vpn vpn = mVpns.get(UserHandle.getUserId(uid)); if (vpn != null && vpn.isBlockingUid(uid)) { @@ -1150,6 +1169,17 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetworkInfoBlockingLogs.log(action + " " + uid); } + private void maybeLogBlockedStatusChanged(NetworkRequestInfo nri, Network net, + boolean blocked) { + if (nri == null || net == null || !LOGD_BLOCKED_NETWORKINFO) { + return; + } + String action = blocked ? "BLOCKED" : "UNBLOCKED"; + log(String.format("Blocked status changed to %s for %d(%d) on netId %d", blocked, + nri.mUid, nri.request.requestId, net.netId)); + mNetworkInfoBlockingLogs.log(action + " " + nri.mUid); + } + /** * Apply any relevant filters to {@link NetworkState} for the given UID. For * example, this may mark the network as {@link DetailedState#BLOCKED} based @@ -1651,10 +1681,17 @@ public class ConnectivityService extends IConnectivityManager.Stub private final INetworkPolicyListener mPolicyListener = new NetworkPolicyManager.Listener() { @Override public void onUidRulesChanged(int uid, int uidRules) { - // TODO: notify UID when it has requested targeted updates + mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_RULES_CHANGED, uid, uidRules)); } @Override public void onRestrictBackgroundChanged(boolean restrictBackground) { + // caller is NPMS, since we only register with them + if (LOGD_BLOCKED_NETWORKINFO) { + log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")"); + } + mHandler.sendMessage(mHandler.obtainMessage( + EVENT_DATA_SAVER_CHANGED, restrictBackground ? 1 : 0, 0)); + // TODO: relocate this specific callback in Tethering. if (restrictBackground) { log("onRestrictBackgroundChanged(true): disabling tethering"); @@ -1663,6 +1700,50 @@ public class ConnectivityService extends IConnectivityManager.Stub } }; + void handleUidRulesChanged(int uid, int newRules) { + // skip update when we've already applied rules + final int oldRules = mUidRules.get(uid, RULE_NONE); + if (oldRules == newRules) return; + + maybeNotifyNetworkBlockedForNewUidRules(uid, newRules); + + if (newRules == RULE_NONE) { + mUidRules.delete(uid); + } else { + mUidRules.put(uid, newRules); + } + } + + void handleRestrictBackgroundChanged(boolean restrictBackground) { + if (mRestrictBackground == restrictBackground) return; + + for (final NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + final boolean curMetered = nai.networkCapabilities.isMetered(); + maybeNotifyNetworkBlocked(nai, curMetered, curMetered, mRestrictBackground, + restrictBackground); + } + + mRestrictBackground = restrictBackground; + } + + private boolean isUidNetworkingWithVpnBlocked(int uid, int uidRules, boolean isNetworkMetered, + boolean isBackgroundRestricted) { + synchronized (mVpns) { + final Vpn vpn = mVpns.get(UserHandle.getUserId(uid)); + // Because the return value of this function depends on the list of UIDs the + // always-on VPN blocks when in lockdown mode, when the always-on VPN changes that + // list all state depending on the return value of this function has to be recomputed. + // TODO: add a trigger when the always-on VPN sets its blocked UIDs to reevaluate and + // send the necessary onBlockedStatusChanged callbacks. + if (vpn != null && vpn.isBlockingUid(uid)) { + return true; + } + } + + return mPolicyManagerInternal.isUidNetworkingBlocked(uid, uidRules, + isNetworkMetered, isBackgroundRestricted); + } + /** * Require that the caller is either in the same user or has appropriate permission to interact * across users. @@ -2118,6 +2199,28 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.decreaseIndent(); pw.println(); + pw.print("Restrict background: "); + pw.println(mRestrictBackground); + pw.println(); + + pw.println("Status for known UIDs:"); + pw.increaseIndent(); + final int size = mUidRules.size(); + for (int i = 0; i < size; i++) { + // Don't crash if the array is modified while dumping in bugreports. + try { + final int uid = mUidRules.keyAt(i); + final int uidRules = mUidRules.get(uid, RULE_NONE); + pw.println("UID=" + uid + " rules=" + uidRulesToString(uidRules)); + } catch (ArrayIndexOutOfBoundsException e) { + pw.println(" ArrayIndexOutOfBoundsException"); + } catch (ConcurrentModificationException e) { + pw.println(" ConcurrentModificationException"); + } + } + pw.println(); + pw.decreaseIndent(); + pw.println("Network Requests:"); pw.increaseIndent(); dumpNetworkRequests(pw); @@ -3195,6 +3298,12 @@ public class ConnectivityService extends IConnectivityManager.Stub handlePrivateDnsValidationUpdate( (PrivateDnsValidationUpdate) msg.obj); break; + case EVENT_UID_RULES_CHANGED: + handleUidRulesChanged(msg.arg1, msg.arg2); + break; + case EVENT_DATA_SAVER_CHANGED: + handleRestrictBackgroundChanged(toBool(msg.arg1)); + break; } } } @@ -3783,6 +3892,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private void setLockdownTracker(LockdownVpnTracker tracker) { // Shutdown any existing tracker final LockdownVpnTracker existing = mLockdownTracker; + // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the + // necessary onBlockedStatusChanged callbacks. mLockdownTracker = null; if (existing != null) { existing.shutdown(); @@ -4893,12 +5004,20 @@ public class ConnectivityService extends IConnectivityManager.Stub notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } - // Report changes that are interesting for network statistics tracking. if (prevNc != null) { - final boolean meteredChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_METERED) != - newNc.hasCapability(NET_CAPABILITY_NOT_METERED); + final boolean oldMetered = prevNc.isMetered(); + final boolean newMetered = newNc.isMetered(); + final boolean meteredChanged = oldMetered != newMetered; + + if (meteredChanged) { + maybeNotifyNetworkBlocked(nai, oldMetered, newMetered, mRestrictBackground, + mRestrictBackground); + } + final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) != newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); + + // Report changes that are interesting for network statistics tracking. if (meteredChanged || roamingChanged) { notifyIfacesChangedForNetworkStats(); } @@ -5028,6 +5147,8 @@ public class ConnectivityService extends IConnectivityManager.Stub case ConnectivityManager.CALLBACK_AVAILABLE: { putParcelable(bundle, new NetworkCapabilities(networkAgent.networkCapabilities)); putParcelable(bundle, new LinkProperties(networkAgent.linkProperties)); + // For this notification, arg1 contains the blocked status. + msg.arg1 = arg1; break; } case ConnectivityManager.CALLBACK_LOSING: { @@ -5045,6 +5166,10 @@ public class ConnectivityService extends IConnectivityManager.Stub putParcelable(bundle, new LinkProperties(networkAgent.linkProperties)); break; } + case ConnectivityManager.CALLBACK_BLK_CHANGED: { + msg.arg1 = arg1; + break; + } } msg.what = notificationType; msg.setData(bundle); @@ -5600,7 +5725,76 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, 0); + final boolean metered = nai.networkCapabilities.isMetered(); + final boolean blocked = isUidNetworkingWithVpnBlocked(nri.mUid, mUidRules.get(nri.mUid), + metered, mRestrictBackground); + callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0); + } + + /** + * Notify of the blocked state apps with a registered callback matching a given NAI. + * + * Unlike other callbacks, blocked status is different between each individual uid. So for + * any given nai, all requests need to be considered according to the uid who filed it. + * + * @param nai The target NetworkAgentInfo. + * @param oldMetered True if the previous network capabilities is metered. + * @param newRestrictBackground True if data saver is enabled. + */ + private void maybeNotifyNetworkBlocked(NetworkAgentInfo nai, boolean oldMetered, + boolean newMetered, boolean oldRestrictBackground, boolean newRestrictBackground) { + + for (int i = 0; i < nai.numNetworkRequests(); i++) { + NetworkRequest nr = nai.requestAt(i); + NetworkRequestInfo nri = mNetworkRequests.get(nr); + final int uidRules = mUidRules.get(nri.mUid); + final boolean oldBlocked, newBlocked; + // mVpns lock needs to be hold here to ensure that the active VPN cannot be changed + // between these two calls. + synchronized (mVpns) { + oldBlocked = isUidNetworkingWithVpnBlocked(nri.mUid, uidRules, oldMetered, + oldRestrictBackground); + newBlocked = isUidNetworkingWithVpnBlocked(nri.mUid, uidRules, newMetered, + newRestrictBackground); + } + if (oldBlocked != newBlocked) { + callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED, + encodeBool(newBlocked)); + } + } + } + + /** + * Notify apps with a given UID of the new blocked state according to new uid rules. + * @param uid The uid for which the rules changed. + * @param newRules The new rules to apply. + */ + private void maybeNotifyNetworkBlockedForNewUidRules(int uid, int newRules) { + for (final NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + final boolean metered = nai.networkCapabilities.isMetered(); + final boolean oldBlocked, newBlocked; + // TODO: Consider that doze mode or turn on/off battery saver would deliver lots of uid + // rules changed event. And this function actually loop through all connected nai and + // its requests. It seems that mVpns lock will be grabbed frequently in this case. + // Reduce the number of locking or optimize the use of lock are likely needed in future. + synchronized (mVpns) { + oldBlocked = isUidNetworkingWithVpnBlocked( + uid, mUidRules.get(uid), metered, mRestrictBackground); + newBlocked = isUidNetworkingWithVpnBlocked( + uid, newRules, metered, mRestrictBackground); + } + if (oldBlocked == newBlocked) { + return; + } + final int arg = encodeBool(newBlocked); + for (int i = 0; i < nai.numNetworkRequests(); i++) { + NetworkRequest nr = nai.requestAt(i); + NetworkRequestInfo nri = mNetworkRequests.get(nr); + if (nri != null && nri.mUid == uid) { + callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED, arg); + } + } + } } private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) {