Move MultinetworkPolicyTracker to services, where it belongs.

This is necessary so that MultinetworkPolicyTracker can see
DeviceConfig. It will also allow removing reflection from
this file to address a long-standing TODO.

Test: build
Change-Id: I968bf67e0e0189b01515242371599c02bc9e2ca2
This commit is contained in:
Chalard Jean 2022-09-14 21:33:06 +09:00
commit 43aae65caf
6 changed files with 7 additions and 8 deletions

View file

@ -205,7 +205,6 @@ import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
import android.net.resolv.aidl.Nat64PrefixEventParcel;
import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
import android.net.wifi.WifiInfo;
import android.os.BatteryStatsManager;
import android.os.Binder;
@ -272,6 +271,7 @@ import com.android.server.connectivity.FullScore;
import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.LingerMonitor;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.MultinetworkPolicyTracker;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkDiagnostics;
import com.android.server.connectivity.NetworkNotificationManager;

View file

@ -0,0 +1,323 @@
/*
* Copyright (C) 2016 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.
*/
package com.android.server.connectivity;
import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
import static android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE;
import android.annotation.NonNull;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.ConnectivityResources;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
/**
* A class to encapsulate management of the "Smart Networking" capability of
* avoiding bad Wi-Fi when, for example upstream connectivity is lost or
* certain critical link failures occur.
*
* This enables the device to switch to another form of connectivity, like
* mobile, if it's available and working.
*
* The Runnable |avoidBadWifiCallback|, if given, is posted to the supplied
* Handler' whenever the computed "avoid bad wifi" value changes.
*
* Disabling this reverts the device to a level of networking sophistication
* circa 2012-13 by disabling disparate code paths each of which contribute to
* maintaining continuous, working Internet connectivity.
*
* @hide
*/
public class MultinetworkPolicyTracker {
private static String TAG = MultinetworkPolicyTracker.class.getSimpleName();
private final Context mContext;
private final ConnectivityResources mResources;
private final Handler mHandler;
private final Runnable mAvoidBadWifiCallback;
private final List<Uri> mSettingsUris;
private final ContentResolver mResolver;
private final SettingObserver mSettingObserver;
private final BroadcastReceiver mBroadcastReceiver;
private volatile boolean mAvoidBadWifi = true;
private volatile int mMeteredMultipathPreference;
private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private volatile long mTestAllowBadWifiUntilMs = 0;
/**
* Whether to prefer bad wifi to a network that yields to bad wifis, even if it never validated
*
* This setting only makes sense if the system is configured not to avoid bad wifis, i.e.
* if mAvoidBadWifi is true. If it's not, then no network ever yields to bad wifis
* ({@see FullScore#POLICY_YIELD_TO_BAD_WIFI}) and this setting has therefore no effect.
*
* If this is false, when ranking a bad wifi that never validated against cell data (or any
* network that yields to bad wifis), the ranker will prefer cell data. It will prefer wifi
* if wifi loses validation later. This behavior avoids the device losing internet access when
* walking past a wifi network with no internet access.
* This is the default behavior up to Android T, but it can be overridden through an overlay
* to behave like below.
*
* If this is true, then in the same scenario, the ranker will prefer cell data until
* the wifi completes its first validation attempt (or the attempt times out after
* ConnectivityService#PROMPT_UNVALIDATED_DELAY_MS), then it will prefer the wifi even if it
* doesn't provide internet access, unless there is a captive portal on that wifi.
* This is the behavior in U and above.
*/
private boolean mActivelyPreferBadWifi;
// Mainline module can't use internal HandlerExecutor, so add an identical executor here.
private static class HandlerExecutor implements Executor {
@NonNull
private final Handler mHandler;
HandlerExecutor(@NonNull Handler handler) {
mHandler = handler;
}
@Override
public void execute(Runnable command) {
if (!mHandler.post(command)) {
throw new RejectedExecutionException(mHandler + " is shutting down");
}
}
}
// TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
@VisibleForTesting @TargetApi(Build.VERSION_CODES.S)
protected class ActiveDataSubscriptionIdListener extends TelephonyCallback
implements TelephonyCallback.ActiveDataSubscriptionIdListener {
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
mActiveSubId = subId;
reevaluateInternal();
}
}
public MultinetworkPolicyTracker(Context ctx, Handler handler) {
this(ctx, handler, null);
}
public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
mContext = ctx;
mResources = new ConnectivityResources(ctx);
mHandler = handler;
mAvoidBadWifiCallback = avoidBadWifiCallback;
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() {
@Override
public void onReceive(Context context, Intent intent) {
reevaluateInternal();
}
};
updateAvoidBadWifi();
updateMeteredMultipathPreference();
}
// TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
@TargetApi(Build.VERSION_CODES.S)
public void start() {
for (Uri uri : mSettingsUris) {
mResolver.registerContentObserver(uri, false, mSettingObserver);
}
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
mContext.registerReceiverForAllUsers(mBroadcastReceiver, intentFilter,
null /* broadcastPermission */, mHandler);
mContext.getSystemService(TelephonyManager.class).registerTelephonyCallback(
new HandlerExecutor(mHandler), new ActiveDataSubscriptionIdListener());
reevaluate();
}
public void shutdown() {
mResolver.unregisterContentObserver(mSettingObserver);
mContext.unregisterReceiver(mBroadcastReceiver);
}
public boolean getAvoidBadWifi() {
return mAvoidBadWifi;
}
public boolean getActivelyPreferBadWifi() {
return mActivelyPreferBadWifi;
}
// TODO: move this to MultipathPolicyTracker.
public int getMeteredMultipathPreference() {
return mMeteredMultipathPreference;
}
/**
* Whether the device or carrier configuration disables avoiding bad wifi by default.
*/
public boolean configRestrictsAvoidBadWifi() {
final boolean allowBadWifi = mTestAllowBadWifiUntilMs > 0
&& mTestAllowBadWifiUntilMs > System.currentTimeMillis();
// If the config returns true, then avoid bad wifi design can be controlled by the
// NETWORK_AVOID_BAD_WIFI setting.
if (allowBadWifi) return true;
// TODO: use R.integer.config_networkAvoidBadWifi directly
final int id = mResources.get().getIdentifier("config_networkAvoidBadWifi",
"integer", mResources.getResourcesContext().getPackageName());
return (getResourcesForActiveSubId().getInteger(id) == 0);
}
/**
* Whether the device config prefers bad wifi actively, when it doesn't avoid them
*
* This is only relevant when the device is configured not to avoid bad wifis. In this
* case, "actively" preferring a bad wifi means that the device will switch to a bad
* wifi it just connected to, as long as it's not a captive portal.
*
* On U and above this always returns true. On T and below it reads a configuration option.
*/
public boolean configActivelyPrefersBadWifi() {
// See the definition of config_activelyPreferBadWifi for a description of its meaning.
// On U and above, the config is ignored, and bad wifi is always actively preferred.
if (SdkLevel.isAtLeastU()) return true;
// TODO: use R.integer.config_activelyPreferBadWifi directly
final int id = mResources.get().getIdentifier("config_activelyPreferBadWifi",
"integer", mResources.getResourcesContext().getPackageName());
// On T and below, 1 means to actively prefer bad wifi, 0 means not to prefer
// bad wifi (only stay stuck on it if already on there). This implementation treats
// any non-0 value like 1, on the assumption that anybody setting it non-zero wants
// the newer behavior.
return 0 != getResourcesForActiveSubId().getInteger(id);
}
/**
* Temporarily allow bad wifi to override {@code config_networkAvoidBadWifi} configuration.
* The value works when the time set is more than {@link System.currentTimeMillis()}.
*/
public void setTestAllowBadWifiUntil(long timeMs) {
Log.d(TAG, "setTestAllowBadWifiUntil: " + timeMs);
mTestAllowBadWifiUntilMs = timeMs;
reevaluateInternal();
}
@VisibleForTesting
@NonNull
protected Resources getResourcesForActiveSubId() {
return SubscriptionManager.getResourcesForSubId(
mResources.getResourcesContext(), mActiveSubId);
}
/**
* Whether we should display a notification when wifi becomes unvalidated.
*/
public boolean shouldNotifyWifiUnvalidated() {
return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null;
}
public String getAvoidBadWifiSetting() {
return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI);
}
@VisibleForTesting
public void reevaluate() {
mHandler.post(this::reevaluateInternal);
}
/**
* Reevaluate the settings. Must be called on the handler thread.
*/
private void reevaluateInternal() {
if (updateAvoidBadWifi() && mAvoidBadWifiCallback != null) {
mAvoidBadWifiCallback.run();
}
updateMeteredMultipathPreference();
}
public boolean updateAvoidBadWifi() {
final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting());
final boolean prevAvoid = mAvoidBadWifi;
mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
final boolean prevActive = mActivelyPreferBadWifi;
mActivelyPreferBadWifi = configActivelyPrefersBadWifi();
return mAvoidBadWifi != prevAvoid || mActivelyPreferBadWifi != prevActive;
}
/**
* The default (device and carrier-dependent) value for metered multipath preference.
*/
public int configMeteredMultipathPreference() {
// TODO: use R.integer.config_networkMeteredMultipathPreference directly
final int id = mResources.get().getIdentifier("config_networkMeteredMultipathPreference",
"integer", mResources.getResourcesContext().getPackageName());
return mResources.get().getInteger(id);
}
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);
}
@Override
public void onChange(boolean selfChange) {
Log.wtf(TAG, "Should never be reached.");
}
@Override
public void onChange(boolean selfChange, Uri uri) {
if (!mSettingsUris.contains(uri)) {
Log.wtf(TAG, "Unexpected settings observation: " + uri);
}
reevaluate();
}
}
}

View file

@ -40,7 +40,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.util.MultinetworkPolicyTracker;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;

View file

@ -37,7 +37,7 @@ import android.net.NetworkRequest
import android.net.TestNetworkStackClient
import android.net.Uri
import android.net.metrics.IpConnectivityLog
import android.net.util.MultinetworkPolicyTracker
import com.android.server.connectivity.MultinetworkPolicyTracker
import android.os.ConditionVariable
import android.os.IBinder
import android.os.SystemConfigManager

View file

@ -303,7 +303,6 @@ import android.net.networkstack.NetworkStackClientBase;
import android.net.resolv.aidl.Nat64PrefixEventParcel;
import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
import android.net.wifi.WifiInfo;
import android.os.BadParcelableException;
import android.os.BatteryStatsManager;
@ -366,6 +365,7 @@ import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencie
import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
import com.android.server.connectivity.ClatCoordinator;
import com.android.server.connectivity.ConnectivityFlags;
import com.android.server.connectivity.MultinetworkPolicyTracker;
import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkNotificationManager;

View file

@ -0,0 +1,187 @@
/*
* Copyright (C) 2021 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.
*/
package com.android.server.connectivity
import android.content.Context
import android.content.res.Resources
import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER
import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE
import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY
import android.net.ConnectivityResources
import android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI
import android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE
import com.android.server.connectivity.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
import android.os.Build
import android.os.Handler
import android.provider.Settings
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.test.mock.MockContentResolver
import androidx.test.filters.SmallTest
import com.android.connectivity.resources.R
import com.android.internal.util.test.FakeSettingsProvider
import com.android.modules.utils.build.SdkLevel
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.argThat
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.any
import org.mockito.Mockito.doCallRealMethod
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
/**
* Tests for [MultinetworkPolicyTracker].
*
* Build, install and run with:
* atest FrameworksNetTest:MultinetworkPolicyTrackerTest
*/
@RunWith(DevSdkIgnoreRunner::class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class MultinetworkPolicyTrackerTest {
private val resources = mock(Resources::class.java).also {
doReturn(R.integer.config_networkAvoidBadWifi).`when`(it).getIdentifier(
eq("config_networkAvoidBadWifi"), eq("integer"), any())
doReturn(R.integer.config_activelyPreferBadWifi).`when`(it).getIdentifier(
eq("config_activelyPreferBadWifi"), eq("integer"), any())
doReturn(0).`when`(it).getInteger(R.integer.config_networkAvoidBadWifi)
doReturn(0).`when`(it).getInteger(R.integer.config_activelyPreferBadWifi)
}
private val telephonyManager = mock(TelephonyManager::class.java)
private val subscriptionManager = mock(SubscriptionManager::class.java).also {
doReturn(null).`when`(it).getActiveSubscriptionInfo(anyInt())
}
private val resolver = MockContentResolver().apply {
addProvider(Settings.AUTHORITY, FakeSettingsProvider()) }
private val context = mock(Context::class.java).also {
doReturn(Context.TELEPHONY_SERVICE).`when`(it)
.getSystemServiceName(TelephonyManager::class.java)
doReturn(telephonyManager).`when`(it).getSystemService(Context.TELEPHONY_SERVICE)
if (it.getSystemService(TelephonyManager::class.java) == null) {
// Test is using mockito extended
doCallRealMethod().`when`(it).getSystemService(TelephonyManager::class.java)
}
doReturn(subscriptionManager).`when`(it)
.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
doReturn(resolver).`when`(it).contentResolver
doReturn(resources).`when`(it).resources
doReturn(it).`when`(it).createConfigurationContext(any())
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "1")
ConnectivityResources.setResourcesContextForTest(it)
}
private val handler = mock(Handler::class.java)
private val tracker = MultinetworkPolicyTracker(context, handler)
private fun assertMultipathPreference(preference: Int) {
Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
preference.toString())
tracker.updateMeteredMultipathPreference()
assertEquals(preference, tracker.meteredMultipathPreference)
}
@Before
fun setUp() {
tracker.start()
}
@After
fun tearDown() {
ConnectivityResources.setResourcesContextForTest(null)
}
@Test
fun testUpdateMeteredMultipathPreference() {
assertMultipathPreference(MULTIPATH_PREFERENCE_HANDOVER)
assertMultipathPreference(MULTIPATH_PREFERENCE_RELIABILITY)
assertMultipathPreference(MULTIPATH_PREFERENCE_PERFORMANCE)
}
@Test
fun testUpdateAvoidBadWifi() {
doReturn(0).`when`(resources).getInteger(R.integer.config_activelyPreferBadWifi)
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0")
assertTrue(tracker.updateAvoidBadWifi())
assertFalse(tracker.avoidBadWifi)
doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi)
assertTrue(tracker.updateAvoidBadWifi())
assertTrue(tracker.avoidBadWifi)
if (SdkLevel.isAtLeastU()) {
// On U+, the system always prefers bad wifi.
assertTrue(tracker.activelyPreferBadWifi)
} else {
assertFalse(tracker.activelyPreferBadWifi)
}
doReturn(1).`when`(resources).getInteger(R.integer.config_activelyPreferBadWifi)
if (SdkLevel.isAtLeastU()) {
// On U+, this didn't change the setting
assertFalse(tracker.updateAvoidBadWifi())
} else {
// On T-, this must have changed the setting
assertTrue(tracker.updateAvoidBadWifi())
}
// In all cases, now the system actively prefers bad wifi
assertTrue(tracker.activelyPreferBadWifi)
}
@Test
fun testOnActiveDataSubscriptionIdChanged() {
val testSubId = 1000
val subscriptionInfo = SubscriptionInfo(testSubId, ""/* iccId */, 1/* iccId */,
"TMO"/* displayName */, "TMO"/* carrierName */, 1/* nameSource */, 1/* iconTint */,
"123"/* number */, 1/* roaming */, null/* icon */, "310"/* mcc */, "210"/* mnc */,
""/* countryIso */, false/* isEmbedded */, null/* nativeAccessRules */,
"1"/* cardString */)
doReturn(subscriptionInfo).`when`(subscriptionManager).getActiveSubscriptionInfo(testSubId)
// Modify avoidBadWifi and meteredMultipathPreference settings value and local variables in
// MultinetworkPolicyTracker should be also updated after subId changed.
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0")
Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
MULTIPATH_PREFERENCE_PERFORMANCE.toString())
val listenerCaptor = ArgumentCaptor.forClass(
ActiveDataSubscriptionIdListener::class.java)
verify(telephonyManager, times(1))
.registerTelephonyCallback(any(), listenerCaptor.capture())
val listener = listenerCaptor.value
listener.onActiveDataSubscriptionIdChanged(testSubId)
// Check it get resource value with test sub id.
verify(subscriptionManager, times(1)).getActiveSubscriptionInfo(testSubId)
verify(context).createConfigurationContext(argThat { it.mcc == 310 && it.mnc == 210 })
// Check if avoidBadWifi and meteredMultipathPreference values have been updated.
assertFalse(tracker.avoidBadWifi)
assertEquals(MULTIPATH_PREFERENCE_PERFORMANCE, tracker.meteredMultipathPreference)
}
}