blob: 3172c6ec856aa42a71085c11e59cd98e2f05bb12 [file] [log] [blame]
Christopher Wiley497c1472016-10-11 13:26:03 -07001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.connectivity;
18
Erik Klineea9cc482017-03-10 19:35:34 +090019import static org.junit.Assert.assertEquals;
Christopher Wiley497c1472016-10-11 13:26:03 -070020import static org.junit.Assert.assertTrue;
21import static org.mockito.Matchers.anyBoolean;
Erik Klineea9cc482017-03-10 19:35:34 +090022import static org.mockito.Matchers.anyInt;
23import static org.mockito.Matchers.anyString;
Christopher Wiley497c1472016-10-11 13:26:03 -070024import static org.mockito.Matchers.eq;
Erik Kline1fdc2e22017-05-08 17:56:35 +090025import static org.mockito.Mockito.any;
Erik Klineea9cc482017-03-10 19:35:34 +090026import static org.mockito.Mockito.atLeastOnce;
Erik Kline1fdc2e22017-05-08 17:56:35 +090027import static org.mockito.Mockito.doThrow;
Erik Klineea9cc482017-03-10 19:35:34 +090028import static org.mockito.Mockito.times;
29import static org.mockito.Mockito.verify;
30import static org.mockito.Mockito.verifyNoMoreInteractions;
Christopher Wiley497c1472016-10-11 13:26:03 -070031import static org.mockito.Mockito.when;
32
Erik Kline8351faa2017-04-17 16:47:23 +090033import android.content.BroadcastReceiver;
Christopher Wiley497c1472016-10-11 13:26:03 -070034import android.content.Context;
Erik Klineea9cc482017-03-10 19:35:34 +090035import android.content.ContextWrapper;
36import android.content.Intent;
Erik Kline8351faa2017-04-17 16:47:23 +090037import android.content.IntentFilter;
Christopher Wiley497c1472016-10-11 13:26:03 -070038import android.content.res.Resources;
Erik Klineea9cc482017-03-10 19:35:34 +090039import android.hardware.usb.UsbManager;
40import android.net.ConnectivityManager;
41import android.net.ConnectivityManager.NetworkCallback;
Christopher Wiley497c1472016-10-11 13:26:03 -070042import android.net.INetworkPolicyManager;
43import android.net.INetworkStatsService;
Erik Klineea9cc482017-03-10 19:35:34 +090044import android.net.InterfaceConfiguration;
45import android.net.NetworkRequest;
46import android.net.wifi.WifiConfiguration;
47import android.net.wifi.WifiManager;
48import android.os.Handler;
Christopher Wiley497c1472016-10-11 13:26:03 -070049import android.os.INetworkManagementService;
50import android.os.PersistableBundle;
Erik Kline1fdc2e22017-05-08 17:56:35 +090051import android.os.RemoteException;
Christopher Wiley497c1472016-10-11 13:26:03 -070052import android.os.test.TestLooper;
Erik Klineea9cc482017-03-10 19:35:34 +090053import android.os.UserHandle;
Christopher Wiley497c1472016-10-11 13:26:03 -070054import android.support.test.filters.SmallTest;
55import android.support.test.runner.AndroidJUnit4;
56import android.telephony.CarrierConfigManager;
57
Erik Klineea9cc482017-03-10 19:35:34 +090058import com.android.internal.util.test.BroadcastInterceptingContext;
59
Erik Kline8351faa2017-04-17 16:47:23 +090060import org.junit.After;
Christopher Wiley497c1472016-10-11 13:26:03 -070061import org.junit.Before;
62import org.junit.Test;
63import org.junit.runner.RunWith;
64import org.mockito.Mock;
65import org.mockito.MockitoAnnotations;
66
Erik Kline8351faa2017-04-17 16:47:23 +090067import java.util.ArrayList;
68import java.util.Vector;
69
Christopher Wiley497c1472016-10-11 13:26:03 -070070@RunWith(AndroidJUnit4.class)
71@SmallTest
72public class TetheringTest {
73 private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
74
75 @Mock private Context mContext;
Erik Klineea9cc482017-03-10 19:35:34 +090076 @Mock private ConnectivityManager mConnectivityManager;
Christopher Wiley497c1472016-10-11 13:26:03 -070077 @Mock private INetworkManagementService mNMService;
78 @Mock private INetworkStatsService mStatsService;
79 @Mock private INetworkPolicyManager mPolicyManager;
80 @Mock private MockableSystemProperties mSystemProperties;
81 @Mock private Resources mResources;
Erik Klineea9cc482017-03-10 19:35:34 +090082 @Mock private UsbManager mUsbManager;
83 @Mock private WifiManager mWifiManager;
Christopher Wiley497c1472016-10-11 13:26:03 -070084 @Mock private CarrierConfigManager mCarrierConfigManager;
85
86 // Like so many Android system APIs, these cannot be mocked because it is marked final.
87 // We have to use the real versions.
88 private final PersistableBundle mCarrierConfig = new PersistableBundle();
89 private final TestLooper mLooper = new TestLooper();
Erik Klineea9cc482017-03-10 19:35:34 +090090 private final String mTestIfname = "test_wlan0";
Christopher Wiley497c1472016-10-11 13:26:03 -070091
Erik Kline8351faa2017-04-17 16:47:23 +090092 private Vector<Intent> mIntents;
Erik Klineea9cc482017-03-10 19:35:34 +090093 private BroadcastInterceptingContext mServiceContext;
Erik Kline8351faa2017-04-17 16:47:23 +090094 private BroadcastReceiver mBroadcastReceiver;
Christopher Wiley497c1472016-10-11 13:26:03 -070095 private Tethering mTethering;
96
Erik Klineea9cc482017-03-10 19:35:34 +090097 private class MockContext extends BroadcastInterceptingContext {
98 MockContext(Context base) {
99 super(base);
100 }
101
102 @Override
103 public Resources getResources() { return mResources; }
104
105 @Override
106 public Object getSystemService(String name) {
107 if (Context.CONNECTIVITY_SERVICE.equals(name)) return mConnectivityManager;
108 if (Context.WIFI_SERVICE.equals(name)) return mWifiManager;
109 return super.getSystemService(name);
110 }
111 }
112
Erik Kline8351faa2017-04-17 16:47:23 +0900113 @Before
114 public void setUp() throws Exception {
Christopher Wiley497c1472016-10-11 13:26:03 -0700115 MockitoAnnotations.initMocks(this);
Christopher Wiley497c1472016-10-11 13:26:03 -0700116 when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range))
117 .thenReturn(new String[0]);
118 when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs))
119 .thenReturn(new String[0]);
120 when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs))
Erik Klineea9cc482017-03-10 19:35:34 +0900121 .thenReturn(new String[]{ "test_wlan\\d" });
Christopher Wiley497c1472016-10-11 13:26:03 -0700122 when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
123 .thenReturn(new String[0]);
124 when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
125 .thenReturn(new int[0]);
Erik Klineea9cc482017-03-10 19:35:34 +0900126 when(mNMService.listInterfaces())
127 .thenReturn(new String[]{ "test_rmnet_data0", mTestIfname });
128 when(mNMService.getInterfaceConfig(anyString()))
129 .thenReturn(new InterfaceConfiguration());
130
131 mServiceContext = new MockContext(mContext);
Erik Kline8351faa2017-04-17 16:47:23 +0900132 mIntents = new Vector<>();
133 mBroadcastReceiver = new BroadcastReceiver() {
134 @Override
135 public void onReceive(Context context, Intent intent) {
136 mIntents.addElement(intent);
137 }
138 };
139 mServiceContext.registerReceiver(mBroadcastReceiver,
140 new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
Erik Klineea9cc482017-03-10 19:35:34 +0900141 mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager,
Christopher Wiley497c1472016-10-11 13:26:03 -0700142 mLooper.getLooper(), mSystemProperties);
143 }
144
Erik Kline8351faa2017-04-17 16:47:23 +0900145 @After
146 public void tearDown() {
147 mServiceContext.unregisterReceiver(mBroadcastReceiver);
148 }
149
Christopher Wiley497c1472016-10-11 13:26:03 -0700150 private void setupForRequiredProvisioning() {
151 // Produce some acceptable looking provision app setting if requested.
152 when(mResources.getStringArray(
153 com.android.internal.R.array.config_mobile_hotspot_provision_app))
154 .thenReturn(PROVISIONING_APP_NAME);
155 // Don't disable tethering provisioning unless requested.
156 when(mSystemProperties.getBoolean(eq(Tethering.DISABLE_PROVISIONING_SYSPROP_KEY),
157 anyBoolean())).thenReturn(false);
158 // Act like the CarrierConfigManager is present and ready unless told otherwise.
159 when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
160 .thenReturn(mCarrierConfigManager);
161 when(mCarrierConfigManager.getConfig()).thenReturn(mCarrierConfig);
162 mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true);
163 }
164
165 @Test
166 public void canRequireProvisioning() {
167 setupForRequiredProvisioning();
168 assertTrue(mTethering.isTetherProvisioningRequired());
169 }
170
171 @Test
172 public void toleratesCarrierConfigManagerMissing() {
173 setupForRequiredProvisioning();
174 when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
175 .thenReturn(null);
176 // Couldn't get the CarrierConfigManager, but still had a declared provisioning app.
177 // We therefore still require provisioning.
178 assertTrue(mTethering.isTetherProvisioningRequired());
179 }
180
181 @Test
182 public void toleratesCarrierConfigMissing() {
183 setupForRequiredProvisioning();
184 when(mCarrierConfigManager.getConfig()).thenReturn(null);
185 // We still have a provisioning app configured, so still require provisioning.
186 assertTrue(mTethering.isTetherProvisioningRequired());
187 }
188
189 @Test
190 public void provisioningNotRequiredWhenAppNotFound() {
191 setupForRequiredProvisioning();
192 when(mResources.getStringArray(
193 com.android.internal.R.array.config_mobile_hotspot_provision_app))
194 .thenReturn(null);
195 assertTrue(!mTethering.isTetherProvisioningRequired());
196 when(mResources.getStringArray(
197 com.android.internal.R.array.config_mobile_hotspot_provision_app))
198 .thenReturn(new String[] {"malformedApp"});
199 assertTrue(!mTethering.isTetherProvisioningRequired());
200 }
Erik Klineea9cc482017-03-10 19:35:34 +0900201
202 private void sendWifiApStateChanged(int state) {
203 final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
204 intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, state);
205 mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
206 }
207
Erik Kline8351faa2017-04-17 16:47:23 +0900208 private void verifyInterfaceServingModeStarted() throws Exception {
209 verify(mNMService, times(1)).listInterfaces();
210 verify(mNMService, times(1)).getInterfaceConfig(mTestIfname);
211 verify(mNMService, times(1))
212 .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
213 verify(mNMService, times(1)).tetherInterface(mTestIfname);
214 }
215
216 private void verifyTetheringBroadcast(String ifname, String whichExtra) {
217 // Verify that ifname is in the whichExtra array of the tether state changed broadcast.
218 final Intent bcast = mIntents.get(0);
219 assertEquals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED, bcast.getAction());
220 final ArrayList<String> ifnames = bcast.getStringArrayListExtra(whichExtra);
221 assertTrue(ifnames.contains(ifname));
222 mIntents.remove(bcast);
223 }
224
Erik Klineea9cc482017-03-10 19:35:34 +0900225 @Test
226 public void workingLocalOnlyHotspot() throws Exception {
227 when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
Erik Klineea9cc482017-03-10 19:35:34 +0900228
229 // Emulate externally-visible WifiManager effects, causing the
230 // per-interface state machine to start up, and telling us that
231 // hotspot mode is to be started.
232 mTethering.interfaceStatusChanged(mTestIfname, true);
233 sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_ENABLED);
234 mLooper.dispatchAll();
235
Erik Kline8351faa2017-04-17 16:47:23 +0900236 verifyInterfaceServingModeStarted();
237 verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
Erik Klineea9cc482017-03-10 19:35:34 +0900238 verify(mNMService, times(1)).setIpForwardingEnabled(true);
239 verify(mNMService, times(1)).startTethering(any(String[].class));
240 verifyNoMoreInteractions(mNMService);
Erik Kline216af6d2017-04-27 20:57:23 +0900241 verify(mWifiManager).updateInterfaceIpState(
242 mTestIfname, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
243 verifyNoMoreInteractions(mWifiManager);
Erik Kline8351faa2017-04-17 16:47:23 +0900244 verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY);
Erik Klineea9cc482017-03-10 19:35:34 +0900245 // UpstreamNetworkMonitor will be started, and will register two callbacks:
246 // a "listen all" and a "track default".
247 verify(mConnectivityManager, times(1)).registerNetworkCallback(
248 any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
249 verify(mConnectivityManager, times(1)).registerDefaultNetworkCallback(
250 any(NetworkCallback.class), any(Handler.class));
251 // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
252 verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
253 verifyNoMoreInteractions(mConnectivityManager);
254
255 // Emulate externally-visible WifiManager effects, when hotspot mode
256 // is being torn down.
257 sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
258 mTethering.interfaceRemoved(mTestIfname);
259 mLooper.dispatchAll();
260
261 verify(mNMService, times(1)).untetherInterface(mTestIfname);
262 // TODO: Why is {g,s}etInterfaceConfig() called more than once?
263 verify(mNMService, atLeastOnce()).getInterfaceConfig(mTestIfname);
264 verify(mNMService, atLeastOnce())
265 .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
266 verify(mNMService, times(1)).stopTethering();
267 verify(mNMService, times(1)).setIpForwardingEnabled(false);
268 verifyNoMoreInteractions(mNMService);
Erik Kline216af6d2017-04-27 20:57:23 +0900269 verifyNoMoreInteractions(mWifiManager);
Erik Klineea9cc482017-03-10 19:35:34 +0900270 // Asking for the last error after the per-interface state machine
271 // has been reaped yields an unknown interface error.
272 assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE,
273 mTethering.getLastTetherError(mTestIfname));
274 }
275
276 @Test
277 public void workingWifiTethering() throws Exception {
278 when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
Erik Klineceb54c62017-04-18 14:22:25 +0900279 when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
Erik Klineea9cc482017-03-10 19:35:34 +0900280
281 // Emulate pressing the WiFi tethering button.
282 mTethering.startTethering(ConnectivityManager.TETHERING_WIFI, null, false);
283 mLooper.dispatchAll();
Erik Klineceb54c62017-04-18 14:22:25 +0900284 verify(mWifiManager, times(1)).startSoftAp(null);
Erik Klineea9cc482017-03-10 19:35:34 +0900285 verifyNoMoreInteractions(mWifiManager);
286 verifyNoMoreInteractions(mConnectivityManager);
287 verifyNoMoreInteractions(mNMService);
288
289 // Emulate externally-visible WifiManager effects, causing the
290 // per-interface state machine to start up, and telling us that
291 // tethering mode is to be started.
292 mTethering.interfaceStatusChanged(mTestIfname, true);
293 sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_ENABLED);
294 mLooper.dispatchAll();
295
Erik Kline8351faa2017-04-17 16:47:23 +0900296 verifyInterfaceServingModeStarted();
297 verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
Erik Klineea9cc482017-03-10 19:35:34 +0900298 verify(mNMService, times(1)).setIpForwardingEnabled(true);
299 verify(mNMService, times(1)).startTethering(any(String[].class));
300 verifyNoMoreInteractions(mNMService);
Erik Kline216af6d2017-04-27 20:57:23 +0900301 verify(mWifiManager).updateInterfaceIpState(
302 mTestIfname, WifiManager.IFACE_IP_MODE_TETHERED);
303 verifyNoMoreInteractions(mWifiManager);
Erik Kline8351faa2017-04-17 16:47:23 +0900304 verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_ACTIVE_TETHER);
Erik Klineea9cc482017-03-10 19:35:34 +0900305 // UpstreamNetworkMonitor will be started, and will register two callbacks:
306 // a "listen all" and a "track default".
307 verify(mConnectivityManager, times(1)).registerNetworkCallback(
308 any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
309 verify(mConnectivityManager, times(1)).registerDefaultNetworkCallback(
310 any(NetworkCallback.class), any(Handler.class));
311 // In tethering mode, in the default configuration, an explicit request
312 // for a mobile network is also made.
313 verify(mConnectivityManager, atLeastOnce()).getNetworkInfo(anyInt());
314 verify(mConnectivityManager, times(1)).requestNetwork(
315 any(NetworkRequest.class), any(NetworkCallback.class), eq(0), anyInt(),
316 any(Handler.class));
317 // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
318 verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
319 verifyNoMoreInteractions(mConnectivityManager);
320
321 /////
322 // We do not currently emulate any upstream being found.
323 //
324 // This is why there are no calls to verify mNMService.enableNat() or
325 // mNMService.startInterfaceForwarding().
326 /////
327
328 // Emulate pressing the WiFi tethering button.
329 mTethering.stopTethering(ConnectivityManager.TETHERING_WIFI);
330 mLooper.dispatchAll();
Erik Klineceb54c62017-04-18 14:22:25 +0900331 verify(mWifiManager, times(1)).stopSoftAp();
Erik Klineea9cc482017-03-10 19:35:34 +0900332 verifyNoMoreInteractions(mWifiManager);
333 verifyNoMoreInteractions(mConnectivityManager);
334 verifyNoMoreInteractions(mNMService);
335
336 // Emulate externally-visible WifiManager effects, when tethering mode
337 // is being torn down.
338 sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
339 mTethering.interfaceRemoved(mTestIfname);
340 mLooper.dispatchAll();
341
342 verify(mNMService, times(1)).untetherInterface(mTestIfname);
343 // TODO: Why is {g,s}etInterfaceConfig() called more than once?
344 verify(mNMService, atLeastOnce()).getInterfaceConfig(mTestIfname);
345 verify(mNMService, atLeastOnce())
346 .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
347 verify(mNMService, times(1)).stopTethering();
348 verify(mNMService, times(1)).setIpForwardingEnabled(false);
349 verifyNoMoreInteractions(mNMService);
Erik Kline216af6d2017-04-27 20:57:23 +0900350 verifyNoMoreInteractions(mWifiManager);
Erik Klineea9cc482017-03-10 19:35:34 +0900351 // Asking for the last error after the per-interface state machine
352 // has been reaped yields an unknown interface error.
353 assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE,
354 mTethering.getLastTetherError(mTestIfname));
355 }
356
Erik Kline1fdc2e22017-05-08 17:56:35 +0900357 @Test
358 public void failureEnablingIpForwarding() throws Exception {
359 when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
360 when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
361 doThrow(new RemoteException()).when(mNMService).setIpForwardingEnabled(true);
362
363 // Emulate pressing the WiFi tethering button.
364 mTethering.startTethering(ConnectivityManager.TETHERING_WIFI, null, false);
365 mLooper.dispatchAll();
366 verify(mWifiManager, times(1)).startSoftAp(null);
367 verifyNoMoreInteractions(mWifiManager);
368 verifyNoMoreInteractions(mConnectivityManager);
369 verifyNoMoreInteractions(mNMService);
370
371 // Emulate externally-visible WifiManager effects, causing the
372 // per-interface state machine to start up, and telling us that
373 // tethering mode is to be started.
374 mTethering.interfaceStatusChanged(mTestIfname, true);
375 sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_ENABLED);
376 mLooper.dispatchAll();
377
378 // Activity caused by test_wlan0 becoming available.
379 verify(mNMService, times(1)).listInterfaces();
380 // We verify get/set called twice here: once for setup and once during
381 // teardown because all events happen over the course of the single
382 // dispatchAll() above.
383 verify(mNMService, times(2)).getInterfaceConfig(mTestIfname);
384 verify(mNMService, times(2))
385 .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
386 verify(mNMService, times(1)).tetherInterface(mTestIfname);
387 verify(mWifiManager).updateInterfaceIpState(
388 mTestIfname, WifiManager.IFACE_IP_MODE_TETHERED);
389 verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
390 verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
391 // This is called, but will throw.
392 verify(mNMService, times(1)).setIpForwardingEnabled(true);
393 // This never gets called because of the exception thrown above.
394 verify(mNMService, times(0)).startTethering(any(String[].class));
395 // When the master state machine transitions to an error state it tells
396 // downstream interfaces, which causes us to tell Wi-Fi about the error
397 // so it can take down AP mode.
398 verify(mNMService, times(1)).untetherInterface(mTestIfname);
399 verify(mWifiManager).updateInterfaceIpState(
400 mTestIfname, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
401
402 verifyNoMoreInteractions(mWifiManager);
403 verifyNoMoreInteractions(mConnectivityManager);
404 verifyNoMoreInteractions(mNMService);
405 }
406
407 // TODO: Test that a request for hotspot mode doesn't interfere with an
Erik Klineea9cc482017-03-10 19:35:34 +0900408 // already operating tethering mode interface.
Christopher Wiley497c1472016-10-11 13:26:03 -0700409}