diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index ac0c0dc5a9..222953e3ae 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -3263,6 +3263,75 @@ public class ConnectivityManager { } } + /** + * It is acceptable to briefly use multipath data to provide seamless connectivity for + * time-sensitive user-facing operations when the system default network is temporarily + * unresponsive. The amount of data should be limited (less than one megabyte), and the + * operation should be infrequent to ensure that data usage is limited. + * + * An example of such an operation might be a time-sensitive foreground activity, such as a + * voice command, that the user is performing while walking out of range of a Wi-Fi network. + */ + public static final int MULTIPATH_PREFERENCE_HANDOVER = 1 << 0; + + /** + * It is acceptable to use small amounts of multipath data on an ongoing basis to provide + * a backup channel for traffic that is primarily going over another network. + * + * An example might be maintaining backup connections to peers or servers for the purpose of + * fast fallback if the default network is temporarily unresponsive or disconnects. The traffic + * on backup paths should be negligible compared to the traffic on the main path. + */ + public static final int MULTIPATH_PREFERENCE_RELIABILITY = 1 << 1; + + /** + * It is acceptable to use metered data to improve network latency and performance. + */ + public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 1 << 2; + + /** + * Return value to use for unmetered networks. On such networks we currently set all the flags + * to true. + * @hide + */ + public static final int MULTIPATH_PREFERENCE_UNMETERED = + MULTIPATH_PREFERENCE_HANDOVER | + MULTIPATH_PREFERENCE_RELIABILITY | + MULTIPATH_PREFERENCE_PERFORMANCE; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = { + MULTIPATH_PREFERENCE_HANDOVER, + MULTIPATH_PREFERENCE_RELIABILITY, + MULTIPATH_PREFERENCE_PERFORMANCE, + }) + public @interface MultipathPreference { + } + + /** + * Provides a hint to the calling application on whether it is desirable to use the + * multinetwork APIs (e.g., {@link Network#openConnection}, {@link Network#bindSocket}, etc.) + * for multipath data transfer on this network when it is not the system default network. + * Applications desiring to use multipath network protocols should call this method before + * each such operation. + *

+ * This method requires the caller to hold the permission + * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. + * + * @param network The network on which the application desires to use multipath data. + * If {@code null}, this method will return the a preference that will generally + * apply to metered networks. + * @return a bitwise OR of zero or more of the {@code MULTIPATH_PREFERENCE_*} constants. + */ + public @MultipathPreference int getMultipathPreference(Network network) { + try { + return mService.getMultipathPreference(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Resets all connectivity manager settings back to factory defaults. * @hide diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 4aabda9eb0..117fa0b476 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -163,6 +163,8 @@ interface IConnectivityManager void setAcceptUnvalidated(in Network network, boolean accept, boolean always); void setAvoidUnvalidated(in Network network); + int getMultipathPreference(in Network Network); + int getRestoreDefaultNetworkDelay(int networkType); boolean addVpnAddress(String address, int prefixLength); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index ebdd8e42aa..719a64e322 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2882,6 +2882,18 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + @Override + public int getMultipathPreference(Network network) { + enforceAccessPermission(); + + NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); + if (nai != null && !nai.networkInfo.isMetered()) { + return ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED; + } + + return mMultinetworkPolicyTracker.getMeteredMultipathPreference(); + } + private class InternalHandler extends Handler { public InternalHandler(Looper looper) { super(looper); diff --git a/services/net/java/android/net/util/MultinetworkPolicyTracker.java b/services/net/java/android/net/util/MultinetworkPolicyTracker.java index ebd131bebb..424e40d209 100644 --- a/services/net/java/android/net/util/MultinetworkPolicyTracker.java +++ b/services/net/java/android/net/util/MultinetworkPolicyTracker.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; +import android.net.ConnectivityManager; import android.net.Uri; import android.os.Handler; import android.os.Message; @@ -29,10 +30,14 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; +import java.util.Arrays; +import java.util.List; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.R; import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI; +import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; /** * A class to encapsulate management of the "Smart Networking" capability of @@ -57,12 +62,13 @@ public class MultinetworkPolicyTracker { private final Context mContext; private final Handler mHandler; private final Runnable mReevaluateRunnable; - private final Uri mAvoidBadWifiUri; + private final List mSettingsUris; private final ContentResolver mResolver; private final SettingObserver mSettingObserver; private final BroadcastReceiver mBroadcastReceiver; private volatile boolean mAvoidBadWifi = true; + private volatile int mMeteredMultipathPreference; public MultinetworkPolicyTracker(Context ctx, Handler handler) { this(ctx, handler, null); @@ -72,9 +78,14 @@ public class MultinetworkPolicyTracker { mContext = ctx; mHandler = handler; mReevaluateRunnable = () -> { - if (updateAvoidBadWifi() && avoidBadWifiCallback != null) avoidBadWifiCallback.run(); + if (updateAvoidBadWifi() && avoidBadWifiCallback != null) { + avoidBadWifiCallback.run(); + } + updateMeteredMultipathPreference(); }; - mAvoidBadWifiUri = Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI); + mSettingsUris = Arrays.asList( + Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI), + Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE)); mResolver = mContext.getContentResolver(); mSettingObserver = new SettingObserver(); mBroadcastReceiver = new BroadcastReceiver() { @@ -85,10 +96,13 @@ public class MultinetworkPolicyTracker { }; updateAvoidBadWifi(); + updateMeteredMultipathPreference(); } public void start() { - mResolver.registerContentObserver(mAvoidBadWifiUri, false, mSettingObserver); + for (Uri uri : mSettingsUris) { + mResolver.registerContentObserver(uri, false, mSettingObserver); + } final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); @@ -108,6 +122,10 @@ public class MultinetworkPolicyTracker { return mAvoidBadWifi; } + public int getMeteredMultipathPreference() { + return mMeteredMultipathPreference; + } + /** * Whether the device or carrier configuration disables avoiding bad wifi by default. */ @@ -138,6 +156,23 @@ public class MultinetworkPolicyTracker { return mAvoidBadWifi != prev; } + /** + * The default (device and carrier-dependent) value for metered multipath preference. + */ + public int configMeteredMultipathPreference() { + return mContext.getResources().getInteger( + R.integer.config_networkMeteredMultipathPreference); + } + + public void updateMeteredMultipathPreference() { + String setting = Settings.Global.getString(mResolver, NETWORK_METERED_MULTIPATH_PREFERENCE); + try { + mMeteredMultipathPreference = Integer.parseInt(setting); + } catch (NumberFormatException e) { + mMeteredMultipathPreference = configMeteredMultipathPreference(); + } + } + private class SettingObserver extends ContentObserver { public SettingObserver() { super(null); @@ -150,7 +185,9 @@ public class MultinetworkPolicyTracker { @Override public void onChange(boolean selfChange, Uri uri) { - if (!mAvoidBadWifiUri.equals(uri)) return; + if (!mSettingsUris.contains(uri)) { + Slog.wtf(TAG, "Unexpected settings observation: " + uri); + } reevaluate(); } } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 39406a1174..eeaf26f92f 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -595,6 +595,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker { public volatile boolean configRestrictsAvoidBadWifi; + public volatile int configMeteredMultipathPreference; public WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) { super(c, h, r); @@ -604,6 +605,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { public boolean configRestrictsAvoidBadWifi() { return configRestrictsAvoidBadWifi; } + + @Override + public int configMeteredMultipathPreference() { + return configMeteredMultipathPreference; + } } private class WrappedConnectivityService extends ConnectivityService { @@ -2282,6 +2288,26 @@ public class ConnectivityServiceTest extends AndroidTestCase { mCm.unregisterNetworkCallback(defaultCallback); } + @SmallTest + public void testMeteredMultipathPreferenceSetting() throws Exception { + final ContentResolver cr = mServiceContext.getContentResolver(); + final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker(); + final String settingName = Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; + + for (int config : Arrays.asList(0, 3, 2)) { + for (String setting: Arrays.asList(null, "0", "2", "1")) { + tracker.configMeteredMultipathPreference = config; + Settings.Global.putString(cr, settingName, setting); + tracker.reevaluate(); + mService.waitForIdle(); + + final int expected = (setting != null) ? Integer.parseInt(setting) : config; + String msg = String.format("config=%d, setting=%s", config, setting); + assertEquals(msg, expected, mCm.getMultipathPreference(null)); + } + } + } + /** * Validate that a satisfied network request does not trigger onUnavailable() once the * time-out period expires.