Add a tethering offload settings kill switch
Check for tether_offload_disabled (defaulting to false if not present)
before starting the OffloadController.
Test: as follows
- built
- flashed
- booted
- runtest frameworks-net passes
Bug: 29337859
Bug: 32163131
Bug: 34361337
Change-Id: Ifcc0eb1a41f09ad9e717c9252d2d55e69789669b
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2c1796370..feb9cfe 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7498,6 +7498,15 @@
*/
public static final String TETHER_DUN_APN = "tether_dun_apn";
+ /**
+ * Used to disable trying to talk to any available tethering offload HAL.
+ *
+ * Integer values are interpreted as boolean, and the absence of an explicit setting
+ * is interpreted as |false|.
+ * @hide
+ */
+ public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
+
/**
* List of carrier apps which are whitelisted to prompt the user for install when
* a sim card with matching uicc carrier privilege rules is inserted.
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 8088554..dffa670 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -199,6 +199,7 @@
final Handler smHandler = mTetherMasterSM.getHandler();
mOffloadController = new OffloadController(smHandler,
deps.getOffloadHardwareInterface(smHandler, mLog),
+ mContext.getContentResolver(),
mLog);
mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK, mLog);
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 12899d8..c64e705 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -16,9 +16,13 @@
package com.android.server.connectivity.tethering;
+import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+
+import android.content.ContentResolver;
import android.net.LinkProperties;
-import android.os.Handler;
import android.net.util.SharedLog;
+import android.os.Handler;
+import android.provider.Settings;
/**
* A class to encapsulate the business logic of programming the tethering
@@ -31,19 +35,22 @@
private final Handler mHandler;
private final OffloadHardwareInterface mHwInterface;
+ private final ContentResolver mContentResolver;
private final SharedLog mLog;
private boolean mConfigInitialized;
private boolean mControlInitialized;
private LinkProperties mUpstreamLinkProperties;
- public OffloadController(Handler h, OffloadHardwareInterface hwi, SharedLog log) {
+ public OffloadController(Handler h, OffloadHardwareInterface hwi,
+ ContentResolver contentResolver, SharedLog log) {
mHandler = h;
mHwInterface = hwi;
+ mContentResolver = contentResolver;
mLog = log.forSubComponent(TAG);
}
public void start() {
- if (started()) return;
+ if (isOffloadDisabled() || started()) return;
if (!mConfigInitialized) {
mConfigInitialized = mHwInterface.initOffloadConfig();
@@ -91,6 +98,11 @@
// TODO: public void addDownStream(...)
+ private boolean isOffloadDisabled() {
+ // Defaults to |false| if not present.
+ return (Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0) != 0);
+ }
+
private boolean started() {
return mConfigInitialized && mControlInitialized;
}
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index d5a0b86..241b828 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -31,6 +31,7 @@
import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
@@ -52,11 +53,14 @@
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.os.UserHandle;
+import android.provider.Settings;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.CarrierConfigManager;
+import android.test.mock.MockContentResolver;
import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.connectivity.tethering.OffloadHardwareInterface;
import com.android.server.connectivity.tethering.TetheringDependencies;
@@ -96,6 +100,7 @@
private Vector<Intent> mIntents;
private BroadcastInterceptingContext mServiceContext;
+ private MockContentResolver mContentResolver;
private BroadcastReceiver mBroadcastReceiver;
private Tethering mTethering;
@@ -105,6 +110,12 @@
}
@Override
+ public ContentResolver getContentResolver() { return mContentResolver; }
+
+ @Override
+ public String getPackageName() { return "TetheringTest"; }
+
+ @Override
public Resources getResources() { return mResources; }
@Override
@@ -134,6 +145,8 @@
.thenReturn(new InterfaceConfiguration());
mServiceContext = new MockContext(mContext);
+ mContentResolver = new MockContentResolver(mServiceContext);
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
mIntents = new Vector<>();
mBroadcastReceiver = new BroadcastReceiver() {
@Override
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
new file mode 100644
index 0000000..fb7971e
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 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.tethering;
+
+import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.util.SharedLog;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContentResolver;
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class OffloadControllerTest {
+
+ @Mock private OffloadHardwareInterface mHardware;
+ @Mock private Context mContext;
+ private MockContentResolver mContentResolver;
+
+ @Before public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContentResolver = new MockContentResolver(mContext);
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContext.getPackageName()).thenReturn("OffloadControllerTest");
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ }
+
+ private void setupFunctioningHardwareInterface() {
+ when(mHardware.initOffloadConfig()).thenReturn(true);
+ when(mHardware.initOffloadControl(any(OffloadHardwareInterface.ControlCallback.class)))
+ .thenReturn(true);
+ }
+
+ @Test
+ public void testNoSettingsValueAllowsStart() {
+ setupFunctioningHardwareInterface();
+ try {
+ Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED);
+ fail();
+ } catch (SettingNotFoundException expected) {}
+
+ final OffloadController offload =
+ new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ offload.start();
+
+ final InOrder inOrder = inOrder(mHardware);
+ inOrder.verify(mHardware, times(1)).initOffloadConfig();
+ inOrder.verify(mHardware, times(1)).initOffloadControl(
+ any(OffloadHardwareInterface.ControlCallback.class));
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testSettingsAllowsStart() {
+ setupFunctioningHardwareInterface();
+ Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
+
+ final OffloadController offload =
+ new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ offload.start();
+
+ final InOrder inOrder = inOrder(mHardware);
+ inOrder.verify(mHardware, times(1)).initOffloadConfig();
+ inOrder.verify(mHardware, times(1)).initOffloadControl(
+ any(OffloadHardwareInterface.ControlCallback.class));
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testSettingsDisablesStart() {
+ setupFunctioningHardwareInterface();
+ Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1);
+
+ final OffloadController offload =
+ new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ offload.start();
+
+ final InOrder inOrder = inOrder(mHardware);
+ inOrder.verify(mHardware, never()).initOffloadConfig();
+ inOrder.verify(mHardware, never()).initOffloadControl(anyObject());
+ inOrder.verifyNoMoreInteractions();
+ }
+}