diff options
5 files changed, 160 insertions, 26 deletions
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index d7ecc81ffdba..467eb9b0b0bf 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2505,6 +2505,28 @@ public class ConnectivityManager {      }      /** +     * Returns if the active data network for the given UID is metered. A network +     * is classified as metered when the user is sensitive to heavy data usage on +     * that connection due to monetary costs, data limitations or +     * battery/performance issues. You should check this before doing large +     * data transfers, and warn the user or delay the operation until another +     * network is available. +     * +     * @return {@code true} if large transfers should be avoided, otherwise +     *        {@code false}. +     * +     * @hide +     */ +    @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) +    public boolean isActiveNetworkMeteredForUid(int uid) { +        try { +            return mService.isActiveNetworkMeteredForUid(uid); +        } catch (RemoteException e) { +            throw e.rethrowFromSystemServer(); +        } +    } + +    /**       * If the LockdownVpn mechanism is enabled, updates the vpn       * with a reload of its profile.       * diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index a6fe7389bc72..f11372c2b31c 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -66,6 +66,7 @@ interface IConnectivityManager      NetworkQuotaInfo getActiveNetworkQuotaInfo();      boolean isActiveNetworkMetered(); +    boolean isActiveNetworkMeteredForUid(int uid);      boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c1801b80af0d..6e8c0d4a55c1 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -969,7 +969,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          if (!mLockdownEnabled) {              int user = UserHandle.getUserId(uid);              synchronized (mVpns) { -                Vpn vpn = mVpns.get(user); +                Vpn vpn = getVpn(user);                  if (vpn != null && vpn.appliesToUid(uid)) {                      return vpn.getUnderlyingNetworks();                  } @@ -1017,7 +1017,7 @@ public class ConnectivityService extends IConnectivityManager.Stub              return false;          }          synchronized (mVpns) { -            final Vpn vpn = mVpns.get(UserHandle.getUserId(uid)); +            final Vpn vpn = getVpn(UserHandle.getUserId(uid));              if (vpn != null && vpn.isBlockingUid(uid)) {                  return true;              } @@ -1094,7 +1094,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          final int user = UserHandle.getUserId(uid);          int vpnNetId = NETID_UNSET;          synchronized (mVpns) { -            final Vpn vpn = mVpns.get(user); +            final Vpn vpn = getVpn(user);              if (vpn != null && vpn.appliesToUid(uid)) vpnNetId = vpn.getNetId();          }          NetworkAgentInfo nai; @@ -1224,7 +1224,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          if (!mLockdownEnabled) {              synchronized (mVpns) { -                Vpn vpn = mVpns.get(userId); +                Vpn vpn = getVpn(userId);                  if (vpn != null) {                      Network[] networks = vpn.getUnderlyingNetworks();                      if (networks != null) { @@ -1339,7 +1339,17 @@ public class ConnectivityService extends IConnectivityManager.Stub      public boolean isActiveNetworkMetered() {          enforceAccessPermission(); -        final int uid = Binder.getCallingUid(); +        return isActiveNetworkMeteredCommon(Binder.getCallingUid()); +    } + +    @Override +    public boolean isActiveNetworkMeteredForUid(int uid) { +        enforceConnectivityInternalPermission(); + +        return isActiveNetworkMeteredCommon(uid); +    } + +    private boolean isActiveNetworkMeteredCommon(int uid) {          final NetworkCapabilities caps = getUnfilteredActiveNetworkState(uid).networkCapabilities;          if (caps != null) {              return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); @@ -3414,7 +3424,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          throwIfLockdownEnabled();          synchronized (mVpns) { -            Vpn vpn = mVpns.get(userId); +            Vpn vpn = getVpn(userId);              if (vpn != null) {                  return vpn.prepare(oldPackage, newPackage);              } else { @@ -3441,7 +3451,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          enforceCrossUserPermission(userId);          synchronized (mVpns) { -            Vpn vpn = mVpns.get(userId); +            Vpn vpn = getVpn(userId);              if (vpn != null) {                  vpn.setPackageAuthorization(packageName, authorized);              } @@ -3460,7 +3470,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          throwIfLockdownEnabled();          int user = UserHandle.getUserId(Binder.getCallingUid());          synchronized (mVpns) { -            return mVpns.get(user).establish(config); +            return getVpn(user).establish(config);          }      } @@ -3477,7 +3487,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          }          int user = UserHandle.getUserId(Binder.getCallingUid());          synchronized (mVpns) { -            mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress); +            getVpn(user).startLegacyVpn(profile, mKeyStore, egress);          }      } @@ -3491,7 +3501,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          enforceCrossUserPermission(userId);          synchronized (mVpns) { -            return mVpns.get(userId).getLegacyVpnInfo(); +            return getVpn(userId).getLegacyVpnInfo();          }      } @@ -3555,7 +3565,7 @@ public class ConnectivityService extends IConnectivityManager.Stub      public VpnConfig getVpnConfig(int userId) {          enforceCrossUserPermission(userId);          synchronized (mVpns) { -            Vpn vpn = mVpns.get(userId); +            Vpn vpn = getVpn(userId);              if (vpn != null) {                  return vpn.getVpnConfig();              } else { @@ -3589,7 +3599,7 @@ public class ConnectivityService extends IConnectivityManager.Stub              }              int user = UserHandle.getUserId(Binder.getCallingUid());              synchronized (mVpns) { -                Vpn vpn = mVpns.get(user); +                Vpn vpn = getVpn(user);                  if (vpn == null) {                      Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");                      return false; @@ -3636,7 +3646,7 @@ public class ConnectivityService extends IConnectivityManager.Stub       */      private boolean startAlwaysOnVpn(int userId) {          synchronized (mVpns) { -            Vpn vpn = mVpns.get(userId); +            Vpn vpn = getVpn(userId);              if (vpn == null) {                  // Shouldn't happen as all codepaths that point here should have checked the Vpn                  // exists already. @@ -3654,7 +3664,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          enforceCrossUserPermission(userId);          synchronized (mVpns) { -            Vpn vpn = mVpns.get(userId); +            Vpn vpn = getVpn(userId);              if (vpn == null) {                  Slog.w(TAG, "User " + userId + " has no Vpn configuration");                  return false; @@ -3674,7 +3684,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          }          synchronized (mVpns) { -            Vpn vpn = mVpns.get(userId); +            Vpn vpn = getVpn(userId);              if (vpn == null) {                  Slog.w(TAG, "User " + userId + " has no Vpn configuration");                  return false; @@ -3696,7 +3706,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          enforceCrossUserPermission(userId);          synchronized (mVpns) { -            Vpn vpn = mVpns.get(userId); +            Vpn vpn = getVpn(userId);              if (vpn == null) {                  Slog.w(TAG, "User " + userId + " has no Vpn configuration");                  return null; @@ -3842,22 +3852,38 @@ public class ConnectivityService extends IConnectivityManager.Stub      private void onUserStart(int userId) {          synchronized (mVpns) { -            Vpn userVpn = mVpns.get(userId); +            Vpn userVpn = getVpn(userId);              if (userVpn != null) {                  loge("Starting user already has a VPN");                  return;              }              userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId); -            mVpns.put(userId, userVpn); +            setVpn(userId, userVpn);          }          if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {              updateLockdownVpn();          }      } +    /** @hide */ +    @VisibleForTesting +    Vpn getVpn(int userId) { +        synchronized (mVpns) { +            return mVpns.get(userId); +        } +    } + +    /** @hide */ +    @VisibleForTesting +    void setVpn(int userId, Vpn userVpn) { +        synchronized (mVpns) { +            mVpns.put(userId, userVpn); +        } +    } +      private void onUserStop(int userId) {          synchronized (mVpns) { -            Vpn userVpn = mVpns.get(userId); +            Vpn userVpn = getVpn(userId);              if (userVpn == null) {                  loge("Stopped user has no VPN");                  return; @@ -5429,7 +5455,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          throwIfLockdownEnabled();          int user = UserHandle.getUserId(Binder.getCallingUid());          synchronized (mVpns) { -            return mVpns.get(user).addAddress(address, prefixLength); +            return getVpn(user).addAddress(address, prefixLength);          }      } @@ -5438,7 +5464,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          throwIfLockdownEnabled();          int user = UserHandle.getUserId(Binder.getCallingUid());          synchronized (mVpns) { -            return mVpns.get(user).removeAddress(address, prefixLength); +            return getVpn(user).removeAddress(address, prefixLength);          }      } @@ -5448,7 +5474,7 @@ public class ConnectivityService extends IConnectivityManager.Stub          int user = UserHandle.getUserId(Binder.getCallingUid());          boolean success;          synchronized (mVpns) { -            success = mVpns.get(user).setUnderlyingNetworks(networks); +            success = getVpn(user).setUnderlyingNetworks(networks);          }          if (success) {              notifyIfacesChangedForNetworkStats(); diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java index 78367fe97a54..4d5a920dd54f 100644 --- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java @@ -112,10 +112,8 @@ public final class ConnectivityController extends StateController implements          final boolean connected = (info != null) && info.isConnected();          final boolean connectionUsable = connected && validated; -        final boolean metered = connected && (capabilities != null) -                && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); -        final boolean unmetered = connected && (capabilities != null) -                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); +        final boolean metered = connected && mConnManager.isActiveNetworkMeteredForUid(jobUid); +        final boolean unmetered = connected && !mConnManager.isActiveNetworkMeteredForUid(jobUid);          final boolean notRoaming = connected && (info != null)                  && !info.isRoaming(); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 6674f20317b3..504a2dbc362b 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -22,6 +22,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE;  import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;  import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;  import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.TYPE_VPN;  import static android.net.ConnectivityManager.TYPE_WIFI;  import static android.net.ConnectivityManager.getNetworkTypeName;  import static android.net.NetworkCapabilities.*; @@ -102,6 +103,7 @@ import com.android.server.connectivity.MockableSystemProperties;  import com.android.server.connectivity.NetworkAgentInfo;  import com.android.server.connectivity.NetworkMonitor;  import com.android.server.connectivity.NetworkMonitor.CaptivePortalProbeResult; +import com.android.server.connectivity.Vpn;  import com.android.server.net.NetworkPinner;  import com.android.server.net.NetworkPolicyManagerInternal; @@ -333,6 +335,9 @@ public class ConnectivityServiceTest extends AndroidTestCase {                  case TRANSPORT_WIFI_AWARE:                      mScore = 20;                      break; +                case TRANSPORT_VPN: +                    mScore = 0; +                    break;                  default:                      throw new UnsupportedOperationException("unimplemented network type");              } @@ -868,6 +873,8 @@ public class ConnectivityServiceTest extends AndroidTestCase {                  return TYPE_WIFI;              case TRANSPORT_CELLULAR:                  return TYPE_MOBILE; +            case TRANSPORT_VPN: +                return TYPE_VPN;              default:                  return TYPE_NONE;          } @@ -3447,4 +3454,84 @@ public class ConnectivityServiceTest extends AndroidTestCase {              return;          }      } + +    @SmallTest +    public void testVpnNetworkMetered() { +        final TestNetworkCallback callback = new TestNetworkCallback(); +        mCm.registerDefaultNetworkCallback(callback); + +        final NetworkRequest cellRequest = new NetworkRequest.Builder() +                .addTransportType(TRANSPORT_CELLULAR).build(); +        final TestNetworkCallback cellCallback = new TestNetworkCallback(); +        mCm.registerNetworkCallback(cellRequest, cellCallback); + +        // Setup cellular +        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); +        mCellNetworkAgent.connect(true); +        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); +        cellCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); +        verifyActiveNetwork(TRANSPORT_CELLULAR); + +        // Verify meteredness of cellular +        assertTrue(mCm.isActiveNetworkMetered()); + +        // Setup Wifi +        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); +        mWiFiNetworkAgent.connect(true); +        callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); +        cellCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); +        verifyActiveNetwork(TRANSPORT_WIFI); + +        // Verify meteredness of WiFi +        assertTrue(mCm.isActiveNetworkMetered()); + +        // Verify that setting unmetered on Wifi changes ActiveNetworkMetered +        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); +        callback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent); +        assertFalse(mCm.isActiveNetworkMetered()); + +        // Setup VPN +        final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN); +        vpnNetworkAgent.connect(true); + +        Vpn mockVpn = mock(Vpn.class); +        when(mockVpn.appliesToUid(anyInt())).thenReturn(true); +        when(mockVpn.getNetId()).thenReturn(vpnNetworkAgent.getNetwork().netId); + +        Vpn oldVpn = mService.getVpn(UserHandle.myUserId()); +        mService.setVpn(UserHandle.myUserId(), mockVpn); +        assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + +        // Verify meteredness of VPN on default network +        when(mockVpn.getUnderlyingNetworks()).thenReturn(null); +        assertFalse(mCm.isActiveNetworkMetered()); +        assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + +        // Verify meteredness of VPN on unmetered wifi +        when(mockVpn.getUnderlyingNetworks()) +                .thenReturn(new Network[] {mWiFiNetworkAgent.getNetwork()}); +        assertFalse(mCm.isActiveNetworkMetered()); +        assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + +        // Set WiFi as metered, then check to see that it has been updated on the VPN +        mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); +        callback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent); +        assertTrue(mCm.isActiveNetworkMetered()); +        assertTrue(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + +        // Switch to cellular +        when(mockVpn.getUnderlyingNetworks()) +                .thenReturn(new Network[] {mCellNetworkAgent.getNetwork()}); +        assertTrue(mCm.isActiveNetworkMetered()); +        assertTrue(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + +        // Test unmetered cellular +        mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); +        cellCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); +        assertFalse(mCm.isActiveNetworkMetered()); +        assertFalse(mCm.isActiveNetworkMeteredForUid(Process.myUid())); + +        mService.setVpn(UserHandle.myUserId(), oldVpn); +        mCm.unregisterNetworkCallback(callback); +    }  }  |