From 499a57ad115751a8fa5601f853c8ab6ff5237477 Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Mon, 16 May 2016 16:19:07 -0700 Subject: Fix trivial warnings in Tethering.java ( cherry-pick of 6d475970a200f162ba6845d25bb2a777bcaa611c ) Add some missing @Override annotations. Use template arguments to avoid explicit casting. Ignore unclosed IndentingPrintWriter in dump(). Bug: 28798823 Test: Compiles. Change-Id: I2cb6eb384ca23057c8059f4842b1c0d8248d03f8 --- .../com/android/server/connectivity/Tethering.java | 60 ++++++++++------------ 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 1012f9aa031f..7c1973697af7 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -227,7 +227,7 @@ public class Tethering extends BaseNetworkObserver { int ifaceTypes[] = mContext.getResources().getIntArray( com.android.internal.R.array.config_tether_upstream_types); - Collection upstreamIfaceTypes = new ArrayList(); + Collection upstreamIfaceTypes = new ArrayList<>(); for (int i : ifaceTypes) { upstreamIfaceTypes.add(new Integer(i)); } @@ -644,23 +644,23 @@ public class Tethering extends BaseNetworkObserver { boolean bluetoothTethered = false; synchronized (mPublicSync) { - Set ifaces = mIfaces.keySet(); - for (Object iface : ifaces) { + Set ifaces = mIfaces.keySet(); + for (String iface : ifaces) { TetherInterfaceSM sm = mIfaces.get(iface); if (sm != null) { if (sm.isErrored()) { - erroredList.add((String)iface); + erroredList.add(iface); } else if (sm.isAvailable()) { - availableList.add((String)iface); + availableList.add(iface); } else if (sm.isTethered()) { - if (isUsb((String)iface)) { + if (isUsb(iface)) { usbTethered = true; - } else if (isWifi((String)iface)) { + } else if (isWifi(iface)) { wifiTethered = true; - } else if (isBluetooth((String)iface)) { + } else if (isBluetooth(iface)) { bluetoothTethered = true; } - activeList.add((String)iface); + activeList.add(iface); } } } @@ -952,11 +952,11 @@ public class Tethering extends BaseNetworkObserver { public String[] getTetheredIfaces() { ArrayList list = new ArrayList(); synchronized (mPublicSync) { - Set keys = mIfaces.keySet(); - for (Object key : keys) { + Set keys = mIfaces.keySet(); + for (String key : keys) { TetherInterfaceSM sm = mIfaces.get(key); if (sm.isTethered()) { - list.add((String)key); + list.add(key); } } } @@ -970,11 +970,11 @@ public class Tethering extends BaseNetworkObserver { public String[] getTetherableIfaces() { ArrayList list = new ArrayList(); synchronized (mPublicSync) { - Set keys = mIfaces.keySet(); - for (Object key : keys) { + Set keys = mIfaces.keySet(); + for (String key : keys) { TetherInterfaceSM sm = mIfaces.get(key); if (sm.isAvailable()) { - list.add((String)key); + list.add(key); } } } @@ -992,11 +992,11 @@ public class Tethering extends BaseNetworkObserver { public String[] getErroredIfaces() { ArrayList list = new ArrayList(); synchronized (mPublicSync) { - Set keys = mIfaces.keySet(); - for (Object key : keys) { + Set keys = mIfaces.keySet(); + for (String key : keys) { TetherInterfaceSM sm = mIfaces.get(key); if (sm.isErrored()) { - list.add((String)key); + list.add(key); } } } @@ -1076,6 +1076,7 @@ public class Tethering extends BaseNetworkObserver { setInitialState(mInitialState); } + @Override public String toString() { String res = new String(); res += mIfaceName + " - "; @@ -1442,7 +1443,7 @@ public class Tethering extends BaseNetworkObserver { * could/should be moved here. */ class UpstreamNetworkMonitor { - final HashMap mNetworkMap = new HashMap(); + final HashMap mNetworkMap = new HashMap<>(); NetworkCallback mDefaultNetworkCallback; NetworkCallback mDunTetheringCallback; @@ -1520,12 +1521,6 @@ public class Tethering extends BaseNetworkObserver { static final int EVENT_UPSTREAM_LINKPROPERTIES_CHANGED = BASE_MASTER + 5; static final int EVENT_UPSTREAM_LOST = BASE_MASTER + 6; - // This indicates what a timeout event relates to. A state that - // sends itself a delayed timeout event and handles incoming timeout events - // should inc this when it is entered and whenever it sends a new timeout event. - // We do not flush the old ones. - private int mSequenceNumber; - private State mInitialState; private State mTetherModeAliveState; @@ -1845,17 +1840,17 @@ public class Tethering extends BaseNetworkObserver { config_mobile_hotspot_provision_app_no_ui).isEmpty() == false) { ArrayList tethered = new ArrayList(); synchronized (mPublicSync) { - Set ifaces = mIfaces.keySet(); - for (Object iface : ifaces) { + Set ifaces = mIfaces.keySet(); + for (String iface : ifaces) { TetherInterfaceSM sm = mIfaces.get(iface); if (sm != null && sm.isTethered()) { - if (isUsb((String)iface)) { + if (isUsb(iface)) { tethered.add(new Integer( ConnectivityManager.TETHERING_USB)); - } else if (isWifi((String)iface)) { + } else if (isWifi(iface)) { tethered.add(new Integer( ConnectivityManager.TETHERING_WIFI)); - } else if (isBluetooth((String)iface)) { + } else if (isBluetooth(iface)) { tethered.add(new Integer( ConnectivityManager.TETHERING_BLUETOOTH)); } @@ -2080,9 +2075,11 @@ public class Tethering extends BaseNetworkObserver { } } + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + // Binder.java closes the resource for us. + @SuppressWarnings("resource") final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { pw.println("Permission Denial: can't dump ConnectivityService.Tether " + @@ -2108,6 +2105,5 @@ public class Tethering extends BaseNetworkObserver { pw.decreaseIndent(); } pw.decreaseIndent(); - return; } } -- cgit v1.2.3-59-g8ed1b From b9cb7742ca24e494f5ede70dbb835a6c2140914a Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Tue, 17 May 2016 13:41:56 -0700 Subject: Clean up class members in Tethering.TetherInterfaceSM ( cherry-pick of 7b30e548a58ac96a23033db43c7aac79c86e938c ) Mark final fields as such. Group mutex protected fields together with the mutex that protects them. Always access mutex protected fields under the mutex. Bug: 28798823 Change-Id: I96cdd5e063babb73e9f124107c5576c47801f34b Test: Compiles, Wifi tethering continues to work on angler. --- .../com/android/server/connectivity/Tethering.java | 48 +++++++++++----------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 7c1973697af7..87dcfd3b5f7b 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -264,7 +264,7 @@ public class Tethering extends BaseNetworkObserver { TetherInterfaceSM sm = mIfaces.get(iface); if (up) { if (sm == null) { - sm = new TetherInterfaceSM(iface, mLooper, usb); + sm = new TetherInterfaceSM(iface, mLooper, usb, mPublicSync); mIfaces.put(iface, sm); sm.start(); } @@ -339,7 +339,7 @@ public class Tethering extends BaseNetworkObserver { if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring"); return; } - sm = new TetherInterfaceSM(iface, mLooper, usb); + sm = new TetherInterfaceSM(iface, mLooper, usb, mPublicSync); mIfaces.put(iface, sm); sm.start(); } @@ -1041,28 +1041,26 @@ public class Tethering extends BaseNetworkObserver { // the upstream connection has changed static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12; - private State mDefaultState; + private final State mInitialState; + private final State mStartingState; + private final State mTetheredState; + private final State mUnavailableState; - private State mInitialState; - private State mStartingState; - private State mTetheredState; - - private State mUnavailableState; + private final boolean mUsb; + private final String mIfaceName; + private final Object mMutex; // Protects the fields below. private boolean mAvailable; private boolean mTethered; - int mLastError; - - String mIfaceName; - String mMyUpstreamIfaceName; // may change over time - - boolean mUsb; + private int mLastError; + private String mMyUpstreamIfaceName; // may change over time - TetherInterfaceSM(String name, Looper looper, boolean usb) { + TetherInterfaceSM(String name, Looper looper, boolean usb, Object mutex) { super(name, looper); mIfaceName = name; mUsb = usb; setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); + mMutex = mutex; mInitialState = new InitialState(); addState(mInitialState); @@ -1085,20 +1083,20 @@ public class Tethering extends BaseNetworkObserver { if (current == mStartingState) res += "StartingState"; if (current == mTetheredState) res += "TetheredState"; if (current == mUnavailableState) res += "UnavailableState"; - if (mAvailable) res += " - Available"; - if (mTethered) res += " - Tethered"; - res += " - lastError =" + mLastError; + if (isAvailable()) res += " - Available"; + if (isTethered()) res += " - Tethered"; + res += " - lastError =" + getLastError(); return res; } public int getLastError() { - synchronized (Tethering.this.mPublicSync) { + synchronized (mMutex) { return mLastError; } } private void setLastError(int error) { - synchronized (Tethering.this.mPublicSync) { + synchronized (mMutex) { mLastError = error; if (isErrored()) { @@ -1112,31 +1110,31 @@ public class Tethering extends BaseNetworkObserver { } public boolean isAvailable() { - synchronized (Tethering.this.mPublicSync) { + synchronized (mMutex) { return mAvailable; } } private void setAvailable(boolean available) { - synchronized (Tethering.this.mPublicSync) { + synchronized (mMutex) { mAvailable = available; } } public boolean isTethered() { - synchronized (Tethering.this.mPublicSync) { + synchronized (mMutex) { return mTethered; } } private void setTethered(boolean tethered) { - synchronized (Tethering.this.mPublicSync) { + synchronized (mMutex) { mTethered = tethered; } } public boolean isErrored() { - synchronized (Tethering.this.mPublicSync) { + synchronized (mMutex) { return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR); } } -- cgit v1.2.3-59-g8ed1b From e03fb4459cd3ace301926bdfda75034becfc79bc Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Wed, 18 May 2016 13:45:20 -0700 Subject: Make Tethering.TetherInterfaceSM more self contained ( cherry-pick of 9a509ca728fbe264e1a5a6ccd5b39ffc0b5eceb4 ) Remove references from the class into the containing class: - Move functionality to bring up a USB interface into the class itself. - Extract an interface to wrap methods where TetherInterfaceSM was calling into other private classes of Tethering - Inject necessary dependendencies into TetherInterfaceSM instances. Bug: 28833951 Test: Compiles. Change-Id: Ic96947417cf8734072d027b0f1cba8a1a1be7a74 --- .../com/android/server/connectivity/Tethering.java | 148 +++++++++++---------- .../connectivity/tethering/IControlsTethering.java | 29 ++++ 2 files changed, 110 insertions(+), 67 deletions(-) create mode 100644 services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 87dcfd3b5f7b..ac51a9e267ba 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -70,6 +70,7 @@ import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.server.IoThread; +import com.android.server.connectivity.tethering.IControlsTethering; import com.android.server.net.BaseNetworkObserver; import java.io.FileDescriptor; @@ -92,7 +93,7 @@ import java.util.concurrent.atomic.AtomicInteger; * * TODO - look for parent classes and code sharing */ -public class Tethering extends BaseNetworkObserver { +public class Tethering extends BaseNetworkObserver implements IControlsTethering { private final Context mContext; private final static String TAG = "Tethering"; @@ -264,7 +265,8 @@ public class Tethering extends BaseNetworkObserver { TetherInterfaceSM sm = mIfaces.get(iface); if (up) { if (sm == null) { - sm = new TetherInterfaceSM(iface, mLooper, usb, mPublicSync); + sm = new TetherInterfaceSM(iface, mLooper, usb, mPublicSync, + mNMService, mStatsService, this); mIfaces.put(iface, sm); sm.start(); } @@ -339,7 +341,8 @@ public class Tethering extends BaseNetworkObserver { if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring"); return; } - sm = new TetherInterfaceSM(iface, mLooper, usb, mPublicSync); + sm = new TetherInterfaceSM(iface, mLooper, usb, mPublicSync, + mNMService, mStatsService, this); mIfaces.put(iface, sm); sm.start(); } @@ -632,7 +635,8 @@ public class Tethering extends BaseNetworkObserver { // TODO - move all private methods used only by the state machine into the state machine // to clarify what needs synchronized protection. - private void sendTetherStateChangedBroadcast() { + @Override + public void sendTetherStateChangedBroadcast() { if (!getConnectivityManager().isTetheringSupported()) return; ArrayList availableList = new ArrayList(); @@ -809,44 +813,6 @@ public class Tethering extends BaseNetworkObserver { Log.e(TAG, "unable start or stop USB tethering"); } - // configured when we start tethering and unconfig'd on error or conclusion - private boolean configureUsbIface(boolean enabled) { - if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); - - // toggle the USB interfaces - String[] ifaces = new String[0]; - try { - ifaces = mNMService.listInterfaces(); - } catch (Exception e) { - Log.e(TAG, "Error listing Interfaces", e); - return false; - } - for (String iface : ifaces) { - if (isUsb(iface)) { - InterfaceConfiguration ifcg = null; - try { - ifcg = mNMService.getInterfaceConfig(iface); - if (ifcg != null) { - InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); - ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH)); - if (enabled) { - ifcg.setInterfaceUp(); - } else { - ifcg.setInterfaceDown(); - } - ifcg.clearFlag("running"); - mNMService.setInterfaceConfig(iface, ifcg); - } - } catch (Exception e) { - Log.e(TAG, "Error configuring interface " + iface, e); - return false; - } - } - } - - return true; - } - // TODO - return copies so people can't tamper public String[] getTetherableUsbRegexs() { return mTetherableUsbRegexs; @@ -1014,7 +980,12 @@ public class Tethering extends BaseNetworkObserver { } } - class TetherInterfaceSM extends StateMachine { + /** + * @hide + * + * Tracks the eligibility of a given network interface for tethering. + */ + public static class TetherInterfaceSM extends StateMachine { private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100; // notification from the master SM that it's not in tether mode static final int CMD_TETHER_MODE_DEAD = BASE_IFACE + 1; @@ -1046,6 +1017,10 @@ public class Tethering extends BaseNetworkObserver { private final State mTetheredState; private final State mUnavailableState; + private final INetworkManagementService mNMService; + private final INetworkStatsService mStatsService; + private final IControlsTethering mTetherController; + private final boolean mUsb; private final String mIfaceName; @@ -1055,12 +1030,17 @@ public class Tethering extends BaseNetworkObserver { private int mLastError; private String mMyUpstreamIfaceName; // may change over time - TetherInterfaceSM(String name, Looper looper, boolean usb, Object mutex) { - super(name, looper); - mIfaceName = name; + TetherInterfaceSM(String ifaceName, Looper looper, boolean usb, Object mutex, + INetworkManagementService nMService, INetworkStatsService statsService, + IControlsTethering tetherController) { + super(ifaceName, looper); + mNMService = nMService; + mStatsService = statsService; + mTetherController = tetherController; + mIfaceName = ifaceName; mUsb = usb; - setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); mMutex = mutex; + setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); mInitialState = new InitialState(); addState(mInitialState); @@ -1103,7 +1083,7 @@ public class Tethering extends BaseNetworkObserver { if (mUsb) { // note everything's been unwound by this point so nothing to do on // further error.. - Tethering.this.configureUsbIface(false); + configureUsbIface(false, mIfaceName); } } } @@ -1139,12 +1119,45 @@ public class Tethering extends BaseNetworkObserver { } } + // configured when we start tethering and unconfig'd on error or conclusion + private boolean configureUsbIface(boolean enabled, String iface) { + if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); + + InterfaceConfiguration ifcg = null; + try { + ifcg = mNMService.getInterfaceConfig(iface); + if (ifcg != null) { + InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); + ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH)); + if (enabled) { + ifcg.setInterfaceUp(); + } else { + ifcg.setInterfaceDown(); + } + ifcg.clearFlag("running"); + mNMService.setInterfaceConfig(iface, ifcg); + } + } catch (Exception e) { + Log.e(TAG, "Error configuring interface " + iface, e); + return false; + } + + return true; + } + + private void maybeLogMessage(State state, int what) { + if (DBG) { + Log.d(TAG, state.getName() + " got " + + sMagicDecoderRing.get(what, Integer.toString(what))); + } + } + class InitialState extends State { @Override public void enter() { setAvailable(true); setTethered(false); - sendTetherStateChangedBroadcast(); + mTetherController.sendTetherStateChangedBroadcast(); } @Override @@ -1154,8 +1167,7 @@ public class Tethering extends BaseNetworkObserver { switch (message.what) { case CMD_TETHER_REQUESTED: setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED, - TetherInterfaceSM.this); + mTetherController.notifyInterfaceTetheringReadiness(true, TetherInterfaceSM.this); transitionTo(mStartingState); break; case CMD_INTERFACE_DOWN: @@ -1174,16 +1186,15 @@ public class Tethering extends BaseNetworkObserver { public void enter() { setAvailable(false); if (mUsb) { - if (!Tethering.this.configureUsbIface(true)) { - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, - TetherInterfaceSM.this); + if (!configureUsbIface(true, mIfaceName)) { + mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); transitionTo(mInitialState); return; } } - sendTetherStateChangedBroadcast(); + mTetherController.sendTetherStateChangedBroadcast(); // Skipping StartingState transitionTo(mTetheredState); @@ -1195,10 +1206,9 @@ public class Tethering extends BaseNetworkObserver { switch (message.what) { // maybe a parent class? case CMD_TETHER_UNREQUESTED: - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, - TetherInterfaceSM.this); + mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); if (mUsb) { - if (!Tethering.this.configureUsbIface(false)) { + if (!configureUsbIface(false, mIfaceName)) { setLastErrorAndTransitionToInitialState( ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); break; @@ -1216,8 +1226,7 @@ public class Tethering extends BaseNetworkObserver { ConnectivityManager.TETHER_ERROR_MASTER_ERROR); break; case CMD_INTERFACE_DOWN: - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, - TetherInterfaceSM.this); + mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); transitionTo(mUnavailableState); break; default: @@ -1247,7 +1256,7 @@ public class Tethering extends BaseNetworkObserver { if (DBG) Log.d(TAG, "Tethered " + mIfaceName); setAvailable(false); setTethered(true); - sendTetherStateChangedBroadcast(); + mTetherController.sendTetherStateChangedBroadcast(); } private void cleanupUpstream() { @@ -1294,11 +1303,10 @@ public class Tethering extends BaseNetworkObserver { ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); break; } - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, - TetherInterfaceSM.this); + mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); if (message.what == CMD_TETHER_UNREQUESTED) { if (mUsb) { - if (!Tethering.this.configureUsbIface(false)) { + if (!configureUsbIface(false, mIfaceName)) { setLastError( ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); } @@ -1362,9 +1370,9 @@ public class Tethering extends BaseNetworkObserver { break; } if (DBG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName); - sendTetherStateChangedBroadcast(); + mTetherController.sendTetherStateChangedBroadcast(); if (mUsb) { - if (!Tethering.this.configureUsbIface(false)) { + if (!configureUsbIface(false, mIfaceName)) { setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); } } @@ -1384,7 +1392,7 @@ public class Tethering extends BaseNetworkObserver { setAvailable(false); setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); setTethered(false); - sendTetherStateChangedBroadcast(); + mTetherController.sendTetherStateChangedBroadcast(); } @Override public boolean processMessage(Message message) { @@ -2104,4 +2112,10 @@ public class Tethering extends BaseNetworkObserver { } pw.decreaseIndent(); } + + @Override + public void notifyInterfaceTetheringReadiness(boolean isReady, TetherInterfaceSM who) { + mTetherMasterSM.sendMessage((isReady) ? TetherMasterSM.CMD_TETHER_MODE_REQUESTED + : TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, who); + } } diff --git a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java new file mode 100644 index 000000000000..5eed26cc1597 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java @@ -0,0 +1,29 @@ +/* + * 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.tethering; + +import com.android.server.connectivity.Tethering; + +/** + * @hide + * + * Interface with methods necessary to notify that a given interface is ready for tethering. + */ +public interface IControlsTethering { + void sendTetherStateChangedBroadcast(); + void notifyInterfaceTetheringReadiness(boolean isReady, Tethering.TetherInterfaceSM who); +} \ No newline at end of file -- cgit v1.2.3-59-g8ed1b From d2896669245f0480f2cca46d47f598d2b73a5e14 Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Thu, 19 May 2016 11:54:54 -0700 Subject: Extract TetherInterfaceSM to its own class. ( cherry-pick of e3f93b02bdfde6fffd2bcbb2e1dc0785ce9f8d5a ) Attempt to keep all existing logic in place, except: + Marked a constructor as public, rather than default visible. + Added TAG, DBG, VDBG, and decoder ringer statics. + Moved static constants related to USB IPs into TetherInterfaceSM. Bug: 28833951 Test: WiFi Tethering works on angler. Change-Id: Id961220a9045832354cfe7381e5e9c0d8f54bf90 --- .../com/android/server/connectivity/Tethering.java | 440 +------------------ .../connectivity/tethering/IControlsTethering.java | 4 +- .../connectivity/tethering/TetherInterfaceSM.java | 483 +++++++++++++++++++++ 3 files changed, 485 insertions(+), 442 deletions(-) create mode 100644 services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index ac51a9e267ba..b6eec774bfe9 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -71,6 +71,7 @@ import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.server.IoThread; import com.android.server.connectivity.tethering.IControlsTethering; +import com.android.server.connectivity.tethering.TetherInterfaceSM; import com.android.server.net.BaseNetworkObserver; import java.io.FileDescriptor; @@ -135,9 +136,6 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable)); - private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129"; - private static final int USB_PREFIX_LENGTH = 24; - // USB is 192.168.42.1 and 255.255.255.0 // Wifi is 192.168.43.1 and 255.255.255.0 // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1 @@ -980,442 +978,6 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } } - /** - * @hide - * - * Tracks the eligibility of a given network interface for tethering. - */ - public static class TetherInterfaceSM extends StateMachine { - private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100; - // notification from the master SM that it's not in tether mode - static final int CMD_TETHER_MODE_DEAD = BASE_IFACE + 1; - // request from the user that it wants to tether - static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2; - // request from the user that it wants to untether - static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3; - // notification that this interface is down - static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4; - // notification that this interface is up - static final int CMD_INTERFACE_UP = BASE_IFACE + 5; - // notification from the master SM that it had an error turning on cellular dun - static final int CMD_CELL_DUN_ERROR = BASE_IFACE + 6; - // notification from the master SM that it had trouble enabling IP Forwarding - static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7; - // notification from the master SM that it had trouble disabling IP Forwarding - static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8; - // notification from the master SM that it had trouble starting tethering - static final int CMD_START_TETHERING_ERROR = BASE_IFACE + 9; - // notification from the master SM that it had trouble stopping tethering - static final int CMD_STOP_TETHERING_ERROR = BASE_IFACE + 10; - // notification from the master SM that it had trouble setting the DNS forwarders - static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IFACE + 11; - // the upstream connection has changed - static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12; - - private final State mInitialState; - private final State mStartingState; - private final State mTetheredState; - private final State mUnavailableState; - - private final INetworkManagementService mNMService; - private final INetworkStatsService mStatsService; - private final IControlsTethering mTetherController; - - private final boolean mUsb; - private final String mIfaceName; - - private final Object mMutex; // Protects the fields below. - private boolean mAvailable; - private boolean mTethered; - private int mLastError; - private String mMyUpstreamIfaceName; // may change over time - - TetherInterfaceSM(String ifaceName, Looper looper, boolean usb, Object mutex, - INetworkManagementService nMService, INetworkStatsService statsService, - IControlsTethering tetherController) { - super(ifaceName, looper); - mNMService = nMService; - mStatsService = statsService; - mTetherController = tetherController; - mIfaceName = ifaceName; - mUsb = usb; - mMutex = mutex; - setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); - - mInitialState = new InitialState(); - addState(mInitialState); - mStartingState = new StartingState(); - addState(mStartingState); - mTetheredState = new TetheredState(); - addState(mTetheredState); - mUnavailableState = new UnavailableState(); - addState(mUnavailableState); - - setInitialState(mInitialState); - } - - @Override - public String toString() { - String res = new String(); - res += mIfaceName + " - "; - IState current = getCurrentState(); - if (current == mInitialState) res += "InitialState"; - if (current == mStartingState) res += "StartingState"; - if (current == mTetheredState) res += "TetheredState"; - if (current == mUnavailableState) res += "UnavailableState"; - if (isAvailable()) res += " - Available"; - if (isTethered()) res += " - Tethered"; - res += " - lastError =" + getLastError(); - return res; - } - - public int getLastError() { - synchronized (mMutex) { - return mLastError; - } - } - - private void setLastError(int error) { - synchronized (mMutex) { - mLastError = error; - - if (isErrored()) { - if (mUsb) { - // note everything's been unwound by this point so nothing to do on - // further error.. - configureUsbIface(false, mIfaceName); - } - } - } - } - - public boolean isAvailable() { - synchronized (mMutex) { - return mAvailable; - } - } - - private void setAvailable(boolean available) { - synchronized (mMutex) { - mAvailable = available; - } - } - - public boolean isTethered() { - synchronized (mMutex) { - return mTethered; - } - } - - private void setTethered(boolean tethered) { - synchronized (mMutex) { - mTethered = tethered; - } - } - - public boolean isErrored() { - synchronized (mMutex) { - return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR); - } - } - - // configured when we start tethering and unconfig'd on error or conclusion - private boolean configureUsbIface(boolean enabled, String iface) { - if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); - - InterfaceConfiguration ifcg = null; - try { - ifcg = mNMService.getInterfaceConfig(iface); - if (ifcg != null) { - InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); - ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH)); - if (enabled) { - ifcg.setInterfaceUp(); - } else { - ifcg.setInterfaceDown(); - } - ifcg.clearFlag("running"); - mNMService.setInterfaceConfig(iface, ifcg); - } - } catch (Exception e) { - Log.e(TAG, "Error configuring interface " + iface, e); - return false; - } - - return true; - } - - private void maybeLogMessage(State state, int what) { - if (DBG) { - Log.d(TAG, state.getName() + " got " + - sMagicDecoderRing.get(what, Integer.toString(what))); - } - } - - class InitialState extends State { - @Override - public void enter() { - setAvailable(true); - setTethered(false); - mTetherController.sendTetherStateChangedBroadcast(); - } - - @Override - public boolean processMessage(Message message) { - maybeLogMessage(this, message.what); - boolean retValue = true; - switch (message.what) { - case CMD_TETHER_REQUESTED: - setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); - mTetherController.notifyInterfaceTetheringReadiness(true, TetherInterfaceSM.this); - transitionTo(mStartingState); - break; - case CMD_INTERFACE_DOWN: - transitionTo(mUnavailableState); - break; - default: - retValue = false; - break; - } - return retValue; - } - } - - class StartingState extends State { - @Override - public void enter() { - setAvailable(false); - if (mUsb) { - if (!configureUsbIface(true, mIfaceName)) { - mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); - setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - - transitionTo(mInitialState); - return; - } - } - mTetherController.sendTetherStateChangedBroadcast(); - - // Skipping StartingState - transitionTo(mTetheredState); - } - @Override - public boolean processMessage(Message message) { - maybeLogMessage(this, message.what); - boolean retValue = true; - switch (message.what) { - // maybe a parent class? - case CMD_TETHER_UNREQUESTED: - mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); - if (mUsb) { - if (!configureUsbIface(false, mIfaceName)) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - break; - } - } - transitionTo(mInitialState); - break; - case CMD_CELL_DUN_ERROR: - case CMD_IP_FORWARDING_ENABLE_ERROR: - case CMD_IP_FORWARDING_DISABLE_ERROR: - case CMD_START_TETHERING_ERROR: - case CMD_STOP_TETHERING_ERROR: - case CMD_SET_DNS_FORWARDERS_ERROR: - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_MASTER_ERROR); - break; - case CMD_INTERFACE_DOWN: - mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); - transitionTo(mUnavailableState); - break; - default: - retValue = false; - } - return retValue; - } - } - - class TetheredState extends State { - @Override - public void enter() { - try { - mNMService.tetherInterface(mIfaceName); - } catch (Exception e) { - Log.e(TAG, "Error Tethering: " + e.toString()); - setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR); - - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception ee) { - Log.e(TAG, "Error untethering after failure!" + ee.toString()); - } - transitionTo(mInitialState); - return; - } - if (DBG) Log.d(TAG, "Tethered " + mIfaceName); - setAvailable(false); - setTethered(true); - mTetherController.sendTetherStateChangedBroadcast(); - } - - private void cleanupUpstream() { - if (mMyUpstreamIfaceName != null) { - // note that we don't care about errors here. - // sometimes interfaces are gone before we get - // to remove their rules, which generates errors. - // just do the best we can. - try { - // about to tear down NAT; gather remaining statistics - mStatsService.forceUpdate(); - } catch (Exception e) { - if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString()); - } - try { - mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName); - } catch (Exception e) { - if (VDBG) Log.e( - TAG, "Exception in removeInterfaceForward: " + e.toString()); - } - try { - mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName); - } catch (Exception e) { - if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString()); - } - mMyUpstreamIfaceName = null; - } - return; - } - - @Override - public boolean processMessage(Message message) { - maybeLogMessage(this, message.what); - boolean retValue = true; - boolean error = false; - switch (message.what) { - case CMD_TETHER_UNREQUESTED: - case CMD_INTERFACE_DOWN: - cleanupUpstream(); - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception e) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); - break; - } - mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); - if (message.what == CMD_TETHER_UNREQUESTED) { - if (mUsb) { - if (!configureUsbIface(false, mIfaceName)) { - setLastError( - ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - } - } - transitionTo(mInitialState); - } else if (message.what == CMD_INTERFACE_DOWN) { - transitionTo(mUnavailableState); - } - if (DBG) Log.d(TAG, "Untethered " + mIfaceName); - break; - case CMD_TETHER_CONNECTION_CHANGED: - String newUpstreamIfaceName = (String)(message.obj); - if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) || - (mMyUpstreamIfaceName != null && - mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) { - if (VDBG) Log.d(TAG, "Connection changed noop - dropping"); - break; - } - cleanupUpstream(); - if (newUpstreamIfaceName != null) { - try { - mNMService.enableNat(mIfaceName, newUpstreamIfaceName); - mNMService.startInterfaceForwarding(mIfaceName, - newUpstreamIfaceName); - } catch (Exception e) { - Log.e(TAG, "Exception enabling Nat: " + e.toString()); - try { - mNMService.disableNat(mIfaceName, newUpstreamIfaceName); - } catch (Exception ee) {} - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception ee) {} - - setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR); - transitionTo(mInitialState); - return true; - } - } - mMyUpstreamIfaceName = newUpstreamIfaceName; - break; - case CMD_CELL_DUN_ERROR: - case CMD_IP_FORWARDING_ENABLE_ERROR: - case CMD_IP_FORWARDING_DISABLE_ERROR: - case CMD_START_TETHERING_ERROR: - case CMD_STOP_TETHERING_ERROR: - case CMD_SET_DNS_FORWARDERS_ERROR: - error = true; - // fall through - case CMD_TETHER_MODE_DEAD: - cleanupUpstream(); - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception e) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); - break; - } - if (error) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_MASTER_ERROR); - break; - } - if (DBG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName); - mTetherController.sendTetherStateChangedBroadcast(); - if (mUsb) { - if (!configureUsbIface(false, mIfaceName)) { - setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - } - } - transitionTo(mInitialState); - break; - default: - retValue = false; - break; - } - return retValue; - } - } - - class UnavailableState extends State { - @Override - public void enter() { - setAvailable(false); - setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); - setTethered(false); - mTetherController.sendTetherStateChangedBroadcast(); - } - @Override - public boolean processMessage(Message message) { - boolean retValue = true; - switch (message.what) { - case CMD_INTERFACE_UP: - transitionTo(mInitialState); - break; - default: - retValue = false; - break; - } - return retValue; - } - } - - void setLastErrorAndTransitionToInitialState(int error) { - setLastError(error); - transitionTo(mInitialState); - } - - } - /** * A NetworkCallback class that relays information of interest to the * tethering master state machine thread for subsequent processing. diff --git a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java index 5eed26cc1597..9f4effff29a8 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java +++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java @@ -16,8 +16,6 @@ package com.android.server.connectivity.tethering; -import com.android.server.connectivity.Tethering; - /** * @hide * @@ -25,5 +23,5 @@ import com.android.server.connectivity.Tethering; */ public interface IControlsTethering { void sendTetherStateChangedBroadcast(); - void notifyInterfaceTetheringReadiness(boolean isReady, Tethering.TetherInterfaceSM who); + void notifyInterfaceTetheringReadiness(boolean isReady, TetherInterfaceSM who); } \ No newline at end of file diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java new file mode 100644 index 000000000000..e7f32a95cfad --- /dev/null +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java @@ -0,0 +1,483 @@ +/* + * 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.tethering; + +import android.net.ConnectivityManager; +import android.net.INetworkStatsService; +import android.net.InterfaceConfiguration; +import android.net.LinkAddress; +import android.net.NetworkUtils; +import android.os.INetworkManagementService; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.util.IState; +import com.android.internal.util.MessageUtils; +import com.android.internal.util.Protocol; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.net.InetAddress; + +/** + * @hide + * + * Tracks the eligibility of a given network interface for tethering. + */ +public class TetherInterfaceSM extends StateMachine { + private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129"; + private static final int USB_PREFIX_LENGTH = 24; + + private final static String TAG = "TetherInterfaceSM"; + private final static boolean DBG = false; + private final static boolean VDBG = false; + private static final Class[] messageClasses = { + TetherInterfaceSM.class + }; + private static final SparseArray sMagicDecoderRing = + MessageUtils.findMessageNames(messageClasses); + + private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100; + // notification from the master SM that it's not in tether mode + public static final int CMD_TETHER_MODE_DEAD = BASE_IFACE + 1; + // request from the user that it wants to tether + public static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2; + // request from the user that it wants to untether + public static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3; + // notification that this interface is down + public static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4; + // notification that this interface is up + public static final int CMD_INTERFACE_UP = BASE_IFACE + 5; + // notification from the master SM that it had an error turning on cellular dun + public static final int CMD_CELL_DUN_ERROR = BASE_IFACE + 6; + // notification from the master SM that it had trouble enabling IP Forwarding + public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7; + // notification from the master SM that it had trouble disabling IP Forwarding + public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8; + // notification from the master SM that it had trouble starting tethering + public static final int CMD_START_TETHERING_ERROR = BASE_IFACE + 9; + // notification from the master SM that it had trouble stopping tethering + public static final int CMD_STOP_TETHERING_ERROR = BASE_IFACE + 10; + // notification from the master SM that it had trouble setting the DNS forwarders + public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IFACE + 11; + // the upstream connection has changed + public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12; + + private final State mInitialState; + private final State mStartingState; + private final State mTetheredState; + private final State mUnavailableState; + + private final INetworkManagementService mNMService; + private final INetworkStatsService mStatsService; + private final IControlsTethering mTetherController; + + private final boolean mUsb; + private final String mIfaceName; + + private final Object mMutex; // Protects the fields below. + private boolean mAvailable; + private boolean mTethered; + private int mLastError; + private String mMyUpstreamIfaceName; // may change over time + + public TetherInterfaceSM(String ifaceName, Looper looper, boolean usb, Object mutex, + INetworkManagementService nMService, INetworkStatsService statsService, + IControlsTethering tetherController) { + super(ifaceName, looper); + mNMService = nMService; + mStatsService = statsService; + mTetherController = tetherController; + mIfaceName = ifaceName; + mUsb = usb; + mMutex = mutex; + setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); + + mInitialState = new InitialState(); + addState(mInitialState); + mStartingState = new StartingState(); + addState(mStartingState); + mTetheredState = new TetheredState(); + addState(mTetheredState); + mUnavailableState = new UnavailableState(); + addState(mUnavailableState); + + setInitialState(mInitialState); + } + + @Override + public String toString() { + String res = new String(); + res += mIfaceName + " - "; + IState current = getCurrentState(); + if (current == mInitialState) res += "InitialState"; + if (current == mStartingState) res += "StartingState"; + if (current == mTetheredState) res += "TetheredState"; + if (current == mUnavailableState) res += "UnavailableState"; + if (isAvailable()) res += " - Available"; + if (isTethered()) res += " - Tethered"; + res += " - lastError =" + getLastError(); + return res; + } + + public int getLastError() { + synchronized (mMutex) { + return mLastError; + } + } + + private void setLastError(int error) { + synchronized (mMutex) { + mLastError = error; + + if (isErrored()) { + if (mUsb) { + // note everything's been unwound by this point so nothing to do on + // further error.. + configureUsbIface(false, mIfaceName); + } + } + } + } + + public boolean isAvailable() { + synchronized (mMutex) { + return mAvailable; + } + } + + private void setAvailable(boolean available) { + synchronized (mMutex) { + mAvailable = available; + } + } + + public boolean isTethered() { + synchronized (mMutex) { + return mTethered; + } + } + + private void setTethered(boolean tethered) { + synchronized (mMutex) { + mTethered = tethered; + } + } + + public boolean isErrored() { + synchronized (mMutex) { + return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR); + } + } + + // configured when we start tethering and unconfig'd on error or conclusion + private boolean configureUsbIface(boolean enabled, String iface) { + if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); + + InterfaceConfiguration ifcg = null; + try { + ifcg = mNMService.getInterfaceConfig(iface); + if (ifcg != null) { + InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); + ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH)); + if (enabled) { + ifcg.setInterfaceUp(); + } else { + ifcg.setInterfaceDown(); + } + ifcg.clearFlag("running"); + mNMService.setInterfaceConfig(iface, ifcg); + } + } catch (Exception e) { + Log.e(TAG, "Error configuring interface " + iface, e); + return false; + } + + return true; + } + + private void maybeLogMessage(State state, int what) { + if (DBG) { + Log.d(TAG, state.getName() + " got " + + sMagicDecoderRing.get(what, Integer.toString(what))); + } + } + + class InitialState extends State { + @Override + public void enter() { + setAvailable(true); + setTethered(false); + mTetherController.sendTetherStateChangedBroadcast(); + } + + @Override + public boolean processMessage(Message message) { + maybeLogMessage(this, message.what); + boolean retValue = true; + switch (message.what) { + case CMD_TETHER_REQUESTED: + setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); + mTetherController.notifyInterfaceTetheringReadiness(true, TetherInterfaceSM.this); + transitionTo(mStartingState); + break; + case CMD_INTERFACE_DOWN: + transitionTo(mUnavailableState); + break; + default: + retValue = false; + break; + } + return retValue; + } + } + + class StartingState extends State { + @Override + public void enter() { + setAvailable(false); + if (mUsb) { + if (!configureUsbIface(true, mIfaceName)) { + mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); + setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); + + transitionTo(mInitialState); + return; + } + } + mTetherController.sendTetherStateChangedBroadcast(); + + // Skipping StartingState + transitionTo(mTetheredState); + } + @Override + public boolean processMessage(Message message) { + maybeLogMessage(this, message.what); + boolean retValue = true; + switch (message.what) { + // maybe a parent class? + case CMD_TETHER_UNREQUESTED: + mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); + if (mUsb) { + if (!configureUsbIface(false, mIfaceName)) { + setLastErrorAndTransitionToInitialState( + ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); + break; + } + } + transitionTo(mInitialState); + break; + case CMD_CELL_DUN_ERROR: + case CMD_IP_FORWARDING_ENABLE_ERROR: + case CMD_IP_FORWARDING_DISABLE_ERROR: + case CMD_START_TETHERING_ERROR: + case CMD_STOP_TETHERING_ERROR: + case CMD_SET_DNS_FORWARDERS_ERROR: + setLastErrorAndTransitionToInitialState( + ConnectivityManager.TETHER_ERROR_MASTER_ERROR); + break; + case CMD_INTERFACE_DOWN: + mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); + transitionTo(mUnavailableState); + break; + default: + retValue = false; + } + return retValue; + } + } + + class TetheredState extends State { + @Override + public void enter() { + try { + mNMService.tetherInterface(mIfaceName); + } catch (Exception e) { + Log.e(TAG, "Error Tethering: " + e.toString()); + setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR); + + try { + mNMService.untetherInterface(mIfaceName); + } catch (Exception ee) { + Log.e(TAG, "Error untethering after failure!" + ee.toString()); + } + transitionTo(mInitialState); + return; + } + if (DBG) Log.d(TAG, "Tethered " + mIfaceName); + setAvailable(false); + setTethered(true); + mTetherController.sendTetherStateChangedBroadcast(); + } + + private void cleanupUpstream() { + if (mMyUpstreamIfaceName != null) { + // note that we don't care about errors here. + // sometimes interfaces are gone before we get + // to remove their rules, which generates errors. + // just do the best we can. + try { + // about to tear down NAT; gather remaining statistics + mStatsService.forceUpdate(); + } catch (Exception e) { + if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString()); + } + try { + mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName); + } catch (Exception e) { + if (VDBG) Log.e( + TAG, "Exception in removeInterfaceForward: " + e.toString()); + } + try { + mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName); + } catch (Exception e) { + if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString()); + } + mMyUpstreamIfaceName = null; + } + return; + } + + @Override + public boolean processMessage(Message message) { + maybeLogMessage(this, message.what); + boolean retValue = true; + boolean error = false; + switch (message.what) { + case CMD_TETHER_UNREQUESTED: + case CMD_INTERFACE_DOWN: + cleanupUpstream(); + try { + mNMService.untetherInterface(mIfaceName); + } catch (Exception e) { + setLastErrorAndTransitionToInitialState( + ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); + break; + } + mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); + if (message.what == CMD_TETHER_UNREQUESTED) { + if (mUsb) { + if (!configureUsbIface(false, mIfaceName)) { + setLastError( + ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); + } + } + transitionTo(mInitialState); + } else if (message.what == CMD_INTERFACE_DOWN) { + transitionTo(mUnavailableState); + } + if (DBG) Log.d(TAG, "Untethered " + mIfaceName); + break; + case CMD_TETHER_CONNECTION_CHANGED: + String newUpstreamIfaceName = (String)(message.obj); + if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) || + (mMyUpstreamIfaceName != null && + mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) { + if (VDBG) Log.d(TAG, "Connection changed noop - dropping"); + break; + } + cleanupUpstream(); + if (newUpstreamIfaceName != null) { + try { + mNMService.enableNat(mIfaceName, newUpstreamIfaceName); + mNMService.startInterfaceForwarding(mIfaceName, + newUpstreamIfaceName); + } catch (Exception e) { + Log.e(TAG, "Exception enabling Nat: " + e.toString()); + try { + mNMService.disableNat(mIfaceName, newUpstreamIfaceName); + } catch (Exception ee) {} + try { + mNMService.untetherInterface(mIfaceName); + } catch (Exception ee) {} + + setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR); + transitionTo(mInitialState); + return true; + } + } + mMyUpstreamIfaceName = newUpstreamIfaceName; + break; + case CMD_CELL_DUN_ERROR: + case CMD_IP_FORWARDING_ENABLE_ERROR: + case CMD_IP_FORWARDING_DISABLE_ERROR: + case CMD_START_TETHERING_ERROR: + case CMD_STOP_TETHERING_ERROR: + case CMD_SET_DNS_FORWARDERS_ERROR: + error = true; + // fall through + case CMD_TETHER_MODE_DEAD: + cleanupUpstream(); + try { + mNMService.untetherInterface(mIfaceName); + } catch (Exception e) { + setLastErrorAndTransitionToInitialState( + ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); + break; + } + if (error) { + setLastErrorAndTransitionToInitialState( + ConnectivityManager.TETHER_ERROR_MASTER_ERROR); + break; + } + if (DBG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName); + mTetherController.sendTetherStateChangedBroadcast(); + if (mUsb) { + if (!configureUsbIface(false, mIfaceName)) { + setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); + } + } + transitionTo(mInitialState); + break; + default: + retValue = false; + break; + } + return retValue; + } + } + + class UnavailableState extends State { + @Override + public void enter() { + setAvailable(false); + setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); + setTethered(false); + mTetherController.sendTetherStateChangedBroadcast(); + } + @Override + public boolean processMessage(Message message) { + boolean retValue = true; + switch (message.what) { + case CMD_INTERFACE_UP: + transitionTo(mInitialState); + break; + default: + retValue = false; + break; + } + return retValue; + } + } + + void setLastErrorAndTransitionToInitialState(int error) { + setLastError(error); + transitionTo(mInitialState); + } +} -- cgit v1.2.3-59-g8ed1b From 1ff75bde48fe66d937b99b0655df941b94310fa1 Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Wed, 18 May 2016 16:32:44 -0700 Subject: Add demonstration unittest for TetherInterfaceSM ( cherry-pick of 1cc6ffd2b32846996520fdf43125da3ec2bbdf49 ) Prove that the concept works by writing a trivial test. Bug: 28833951 Test: `runtest frameworks-services -c com.android.server.connectivity.tethering.TetherInterfaceSMTest` Change-Id: I51911cef7f0d5165221845070befd085497c21a6 --- services/tests/servicestests/Android.mk | 1 + .../tethering/TetherInterfaceSMTest.java | 84 ++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index 59c6970d1feb..50e0662ad4d4 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -12,6 +12,7 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_STATIC_JAVA_LIBRARIES := \ + frameworks-base-testutils \ services.core \ services.devicepolicy \ services.net \ diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java new file mode 100644 index 000000000000..8e28fae624cd --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java @@ -0,0 +1,84 @@ +/* + * 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.tethering; + +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.net.INetworkStatsService; +import android.os.INetworkManagementService; +import android.os.test.TestLooper; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + + +public class TetherInterfaceSMTest { + private static final String IFACE_NAME = "testnet1"; + + @Mock private INetworkManagementService mNMService; + @Mock private INetworkStatsService mStatsService; + @Mock private IControlsTethering mTetherHelper; + + private final TestLooper mLooper = new TestLooper(); + private final Object mMutex = new Object(); + private TetherInterfaceSM mTestedSm; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTestedSm = new TetherInterfaceSM(IFACE_NAME, mLooper.getLooper(), false, mMutex, + mNMService, mStatsService, mTetherHelper); + mTestedSm.start(); + } + + @Test + public void shouldDoNothingUntilRequested() { + final int [] NOOP_COMMANDS = { + TetherInterfaceSM.CMD_TETHER_MODE_DEAD, + TetherInterfaceSM.CMD_TETHER_UNREQUESTED, + TetherInterfaceSM.CMD_INTERFACE_UP, + TetherInterfaceSM.CMD_CELL_DUN_ERROR, + TetherInterfaceSM.CMD_IP_FORWARDING_ENABLE_ERROR, + TetherInterfaceSM.CMD_IP_FORWARDING_DISABLE_ERROR, + TetherInterfaceSM.CMD_START_TETHERING_ERROR, + TetherInterfaceSM.CMD_STOP_TETHERING_ERROR, + TetherInterfaceSM.CMD_SET_DNS_FORWARDERS_ERROR, + TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED + }; + for (int command : NOOP_COMMANDS) { + mTestedSm.sendMessage(command); + dispatchUntilIdle(); + // None of those commands should trigger us to request action from + // the rest of the system. + verifyNoMoreInteractions(mNMService); + verifyNoMoreInteractions(mStatsService); + verifyNoMoreInteractions(mTetherHelper); + } + } + + private void dispatchUntilIdle() { + for (int i = 0; i < 100; i++) { + if (mLooper.isIdle()) { + return; + } + mLooper.dispatchAll(); + } + throw new RuntimeException("Failed to clear message loop."); + } +} -- cgit v1.2.3-59-g8ed1b From 7b61d717604cc4227637dfdf3cc43e67e7df5b34 Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Fri, 20 May 2016 13:23:10 -0700 Subject: Expand test coverage of TetherInterfaceSM ( cherry-pick of 1b3830a2afab565a38572ac5c3238a171b2d56d4 ) Add tests for most normal case operations: - State machine startup. - Entering a tethered state (as USB interface and not) - Handling the first notification of an upstream interface - Handle changes in upstream interface - Handle unrequested teardown correctly Bug: 28833951 Test: These tests pass Change-Id: I2c0c9141bab9eaa92faec8de10fa653fb6236a74 --- .../tethering/TetherInterfaceSMTest.java | 199 +++++++++++++++++++-- 1 file changed, 185 insertions(+), 14 deletions(-) diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java index 8e28fae624cd..9fcec0e9bfab 100644 --- a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java +++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java @@ -16,39 +16,81 @@ package com.android.server.connectivity.tethering; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import android.net.INetworkStatsService; +import android.net.InterfaceConfiguration; import android.os.INetworkManagementService; +import android.os.RemoteException; import android.os.test.TestLooper; import org.junit.Before; import org.junit.Test; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class TetherInterfaceSMTest { private static final String IFACE_NAME = "testnet1"; + private static final String UPSTREAM_IFACE = "upstream0"; + private static final String UPSTREAM_IFACE2 = "upstream1"; @Mock private INetworkManagementService mNMService; @Mock private INetworkStatsService mStatsService; @Mock private IControlsTethering mTetherHelper; + @Mock private InterfaceConfiguration mInterfaceConfiguration; private final TestLooper mLooper = new TestLooper(); private final Object mMutex = new Object(); private TetherInterfaceSM mTestedSm; + private void initStateMachine(boolean isUsb) { + mTestedSm = new TetherInterfaceSM(IFACE_NAME, mLooper.getLooper(), isUsb, mMutex, + mNMService, mStatsService, mTetherHelper); + mTestedSm.start(); + // Starting the state machine always puts us in a consistent state and notifies + // the test of the world that we've changed from an unknown to available state. + mLooper.dispatchAll(); + reset(mNMService, mStatsService, mTetherHelper); + } + + private void initTetheredStateMachine(boolean isUsb, String upstreamIface) { + initStateMachine(isUsb); + dispatchCommand(TetherInterfaceSM.CMD_TETHER_REQUESTED); + if (upstreamIface != null) { + dispatchTetherConnectionChanged(upstreamIface); + } + reset(mNMService, mStatsService, mTetherHelper); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + } + + @Test + public void startsOutAvailable() { mTestedSm = new TetherInterfaceSM(IFACE_NAME, mLooper.getLooper(), false, mMutex, mNMService, mStatsService, mTetherHelper); mTestedSm.start(); + mLooper.dispatchAll(); + assertTrue("Should start out available for tethering", mTestedSm.isAvailable()); + assertFalse("Should not be tethered initially", mTestedSm.isTethered()); + assertFalse("Should have no errors initially", mTestedSm.isErrored()); + verify(mTetherHelper).sendTetherStateChangedBroadcast(); + verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService); } @Test public void shouldDoNothingUntilRequested() { + initStateMachine(false); final int [] NOOP_COMMANDS = { TetherInterfaceSM.CMD_TETHER_MODE_DEAD, TetherInterfaceSM.CMD_TETHER_UNREQUESTED, @@ -62,23 +104,152 @@ public class TetherInterfaceSMTest { TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED }; for (int command : NOOP_COMMANDS) { - mTestedSm.sendMessage(command); - dispatchUntilIdle(); - // None of those commands should trigger us to request action from + // None of these commands should trigger us to request action from // the rest of the system. - verifyNoMoreInteractions(mNMService); - verifyNoMoreInteractions(mStatsService); - verifyNoMoreInteractions(mTetherHelper); + dispatchCommand(command); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); } } - private void dispatchUntilIdle() { - for (int i = 0; i < 100; i++) { - if (mLooper.isIdle()) { - return; - } - mLooper.dispatchAll(); - } - throw new RuntimeException("Failed to clear message loop."); + @Test + public void handlesImmediateInterfaceDown() { + initStateMachine(false); + dispatchCommand(TetherInterfaceSM.CMD_INTERFACE_DOWN); + verify(mTetherHelper).sendTetherStateChangedBroadcast(); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when the interface is down", mTestedSm.isAvailable()); + assertFalse("Should not be tethered when the interface is down", mTestedSm.isTethered()); + assertFalse("Should have no errors when the interface goes immediately down", + mTestedSm.isErrored()); + } + + @Test + public void canBeTethered() throws RemoteException { + initStateMachine(false); + dispatchCommand(TetherInterfaceSM.CMD_TETHER_REQUESTED); + InOrder inOrder = inOrder(mTetherHelper, mNMService); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); + // TODO: This broadcast should be removed. When we send this, we are neither + // available nor tethered, which is misleading, since we're transitioning + // from one to the other. + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + inOrder.verify(mNMService).tetherInterface(IFACE_NAME); + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); + assertTrue("Should be in a tethered state", mTestedSm.isTethered()); + assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); + } + + @Test + public void canUnrequestTethering() throws Exception { + initTetheredStateMachine(false, null); + + dispatchCommand(TetherInterfaceSM.CMD_TETHER_UNREQUESTED); + InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); + inOrder.verify(mNMService).untetherInterface(IFACE_NAME); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertTrue("Should be ready for tethering again", mTestedSm.isAvailable()); + assertFalse("Should not be tethered", mTestedSm.isTethered()); + assertFalse("Should have no errors", mTestedSm.isErrored()); + } + + @Test + public void canBeTetheredAsUsb() throws RemoteException { + initStateMachine(true); + + when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); + dispatchCommand(TetherInterfaceSM.CMD_TETHER_REQUESTED); + + InOrder inOrder = inOrder(mTetherHelper, mNMService); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); + inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); + inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); + // TODO: This broadcast should be removed. When we send this, we are neither + // available nor tethered, which is misleading, since we're transitioning + // from one to the other. + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + inOrder.verify(mNMService).tetherInterface(IFACE_NAME); + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); + assertTrue("Should be in a tethered state", mTestedSm.isTethered()); + assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); + } + + @Test + public void handlesFirstUpstreamChange() throws Exception { + initTetheredStateMachine(false, null); + + // Telling the state machine about its upstream interface triggers a little more configuration. + dispatchTetherConnectionChanged(UPSTREAM_IFACE); + InOrder inOrder = inOrder(mNMService); + inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); + assertTrue("Should be in a tethered state", mTestedSm.isTethered()); + assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); + } + + @Test + public void handlesChangingUpstream() throws Exception { + initTetheredStateMachine(false, UPSTREAM_IFACE); + + dispatchTetherConnectionChanged(UPSTREAM_IFACE2); + InOrder inOrder = inOrder(mNMService, mStatsService); + inOrder.verify(mStatsService).forceUpdate(); + inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); + inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); + assertTrue("Should be in a tethered state", mTestedSm.isTethered()); + assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); + } + + @Test + public void canUnrequestTetheringWithUpstream() throws Exception { + initTetheredStateMachine(false, UPSTREAM_IFACE); + + dispatchCommand(TetherInterfaceSM.CMD_TETHER_UNREQUESTED); + InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); + inOrder.verify(mStatsService).forceUpdate(); + inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).untetherInterface(IFACE_NAME); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertTrue("Should be ready for tethering again", mTestedSm.isAvailable()); + assertFalse("Should not be tethered", mTestedSm.isTethered()); + assertFalse("Should have no errors", mTestedSm.isErrored()); + } + + + /** + * Send a command to the state machine under test, and run the event loop to idle. + * + * @param command One of the TetherInterfaceSM.CMD_* constants. + */ + private void dispatchCommand(int command) { + mTestedSm.sendMessage(command); + mLooper.dispatchAll(); + } + + /** + * Special override to tell the state machine that the upstream interface has changed. + * + * @see #dispatchCommand(int) + * @param upstreamIface String name of upstream interface (or null) + */ + private void dispatchTetherConnectionChanged(String upstreamIface) { + mTestedSm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED, upstreamIface); + mLooper.dispatchAll(); } } -- cgit v1.2.3-59-g8ed1b From 3b1d92250303e9e20c48fabf25b435ff79897437 Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Fri, 20 May 2016 16:44:04 -0700 Subject: Remove transient StartingState from TetherInterfaceSM ( cherry-pick of 79e7fde00d74c2b6d329f7341029ea6b04ac2d97 ) This state immediately switched into the TetheredState. We can safely remove it at only the expense of a misleading state broadcast. Bug: 28798823 Test: runtest frameworks-services -c com.android.server.connectivity.tethering.TetherInterfaceSMTest Change-Id: I295d830dbdb11717d6e05161d97787ffe871beb3 --- .../com/android/server/connectivity/Tethering.java | 5 +- .../connectivity/tethering/TetherInterfaceSM.java | 53 +--------------------- .../tethering/TetherInterfaceSMTest.java | 8 ---- 3 files changed, 4 insertions(+), 62 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index b6eec774bfe9..66e6c7e3d0d7 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -90,9 +90,8 @@ import java.util.concurrent.atomic.AtomicInteger; /** * @hide * - * Timeout - * - * TODO - look for parent classes and code sharing + * This class holds much of the business logic to allow Android devices + * to act as IP gateways via USB, BT, and WiFi interfaces. */ public class Tethering extends BaseNetworkObserver implements IControlsTethering { diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java index e7f32a95cfad..a41c46f2def1 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java @@ -80,7 +80,6 @@ public class TetherInterfaceSM extends StateMachine { public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12; private final State mInitialState; - private final State mStartingState; private final State mTetheredState; private final State mUnavailableState; @@ -111,8 +110,6 @@ public class TetherInterfaceSM extends StateMachine { mInitialState = new InitialState(); addState(mInitialState); - mStartingState = new StartingState(); - addState(mStartingState); mTetheredState = new TetheredState(); addState(mTetheredState); mUnavailableState = new UnavailableState(); @@ -127,7 +124,6 @@ public class TetherInterfaceSM extends StateMachine { res += mIfaceName + " - "; IState current = getCurrentState(); if (current == mInitialState) res += "InitialState"; - if (current == mStartingState) res += "StartingState"; if (current == mTetheredState) res += "TetheredState"; if (current == mUnavailableState) res += "UnavailableState"; if (isAvailable()) res += " - Available"; @@ -235,7 +231,7 @@ public class TetherInterfaceSM extends StateMachine { case CMD_TETHER_REQUESTED: setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); mTetherController.notifyInterfaceTetheringReadiness(true, TetherInterfaceSM.this); - transitionTo(mStartingState); + transitionTo(mTetheredState); break; case CMD_INTERFACE_DOWN: transitionTo(mUnavailableState); @@ -248,7 +244,7 @@ public class TetherInterfaceSM extends StateMachine { } } - class StartingState extends State { + class TetheredState extends State { @Override public void enter() { setAvailable(false); @@ -261,51 +257,7 @@ public class TetherInterfaceSM extends StateMachine { return; } } - mTetherController.sendTetherStateChangedBroadcast(); - - // Skipping StartingState - transitionTo(mTetheredState); - } - @Override - public boolean processMessage(Message message) { - maybeLogMessage(this, message.what); - boolean retValue = true; - switch (message.what) { - // maybe a parent class? - case CMD_TETHER_UNREQUESTED: - mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); - if (mUsb) { - if (!configureUsbIface(false, mIfaceName)) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - break; - } - } - transitionTo(mInitialState); - break; - case CMD_CELL_DUN_ERROR: - case CMD_IP_FORWARDING_ENABLE_ERROR: - case CMD_IP_FORWARDING_DISABLE_ERROR: - case CMD_START_TETHERING_ERROR: - case CMD_STOP_TETHERING_ERROR: - case CMD_SET_DNS_FORWARDERS_ERROR: - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_MASTER_ERROR); - break; - case CMD_INTERFACE_DOWN: - mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); - transitionTo(mUnavailableState); - break; - default: - retValue = false; - } - return retValue; - } - } - class TetheredState extends State { - @Override - public void enter() { try { mNMService.tetherInterface(mIfaceName); } catch (Exception e) { @@ -321,7 +273,6 @@ public class TetherInterfaceSM extends StateMachine { return; } if (DBG) Log.d(TAG, "Tethered " + mIfaceName); - setAvailable(false); setTethered(true); mTetherController.sendTetherStateChangedBroadcast(); } diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java index 9fcec0e9bfab..3455afa9fedf 100644 --- a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java +++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java @@ -129,10 +129,6 @@ public class TetherInterfaceSMTest { dispatchCommand(TetherInterfaceSM.CMD_TETHER_REQUESTED); InOrder inOrder = inOrder(mTetherHelper, mNMService); inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); - // TODO: This broadcast should be removed. When we send this, we are neither - // available nor tethered, which is misleading, since we're transitioning - // from one to the other. - inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); inOrder.verify(mNMService).tetherInterface(IFACE_NAME); inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); @@ -168,10 +164,6 @@ public class TetherInterfaceSMTest { inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); - // TODO: This broadcast should be removed. When we send this, we are neither - // available nor tethered, which is misleading, since we're transitioning - // from one to the other. - inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); inOrder.verify(mNMService).tetherInterface(IFACE_NAME); inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); -- cgit v1.2.3-59-g8ed1b From 9ad83ab607c316c85064af5a84eafcadfa91e4bf Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Fri, 20 May 2016 17:51:27 -0700 Subject: Remove dead code from TetherInterfaceSM ( cherry-pick of 2ea4166c74e70d3796b06c91f37eb03eecdf4773 ) Because no code outside of unittests ever issued these commands to TetherInterfaceSM, we never executed any of the removed codepaths. Change-Id: Id54f6e4eaeff8b3486cd78ddcc8c2a31011e6436 Test: Compiles. Unittests continue to pass. Bug: 28798823 --- .../connectivity/tethering/TetherInterfaceSM.java | 46 +++++----------------- .../tethering/TetherInterfaceSMTest.java | 3 -- 2 files changed, 9 insertions(+), 40 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java index a41c46f2def1..cc852d6ea510 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java @@ -54,18 +54,12 @@ public class TetherInterfaceSM extends StateMachine { MessageUtils.findMessageNames(messageClasses); private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100; - // notification from the master SM that it's not in tether mode - public static final int CMD_TETHER_MODE_DEAD = BASE_IFACE + 1; // request from the user that it wants to tether public static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2; // request from the user that it wants to untether public static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3; // notification that this interface is down public static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4; - // notification that this interface is up - public static final int CMD_INTERFACE_UP = BASE_IFACE + 5; - // notification from the master SM that it had an error turning on cellular dun - public static final int CMD_CELL_DUN_ERROR = BASE_IFACE + 6; // notification from the master SM that it had trouble enabling IP Forwarding public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7; // notification from the master SM that it had trouble disabling IP Forwarding @@ -309,7 +303,6 @@ public class TetherInterfaceSM extends StateMachine { public boolean processMessage(Message message) { maybeLogMessage(this, message.what); boolean retValue = true; - boolean error = false; switch (message.what) { case CMD_TETHER_UNREQUESTED: case CMD_INTERFACE_DOWN: @@ -365,15 +358,11 @@ public class TetherInterfaceSM extends StateMachine { } mMyUpstreamIfaceName = newUpstreamIfaceName; break; - case CMD_CELL_DUN_ERROR: case CMD_IP_FORWARDING_ENABLE_ERROR: case CMD_IP_FORWARDING_DISABLE_ERROR: case CMD_START_TETHERING_ERROR: case CMD_STOP_TETHERING_ERROR: case CMD_SET_DNS_FORWARDERS_ERROR: - error = true; - // fall through - case CMD_TETHER_MODE_DEAD: cleanupUpstream(); try { mNMService.untetherInterface(mIfaceName); @@ -382,19 +371,8 @@ public class TetherInterfaceSM extends StateMachine { ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); break; } - if (error) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_MASTER_ERROR); - break; - } - if (DBG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName); - mTetherController.sendTetherStateChangedBroadcast(); - if (mUsb) { - if (!configureUsbIface(false, mIfaceName)) { - setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - } - } - transitionTo(mInitialState); + setLastErrorAndTransitionToInitialState( + ConnectivityManager.TETHER_ERROR_MASTER_ERROR); break; default: retValue = false; @@ -404,6 +382,13 @@ public class TetherInterfaceSM extends StateMachine { } } + /** + * This state is terminal for the per interface state machine. At this + * point, the master state machine should have removed this interface + * specific state machine from its list of possible recipients of + * tethering requests. The state machine itself will hang around until + * the garbage collector finds it. + */ class UnavailableState extends State { @Override public void enter() { @@ -412,19 +397,6 @@ public class TetherInterfaceSM extends StateMachine { setTethered(false); mTetherController.sendTetherStateChangedBroadcast(); } - @Override - public boolean processMessage(Message message) { - boolean retValue = true; - switch (message.what) { - case CMD_INTERFACE_UP: - transitionTo(mInitialState); - break; - default: - retValue = false; - break; - } - return retValue; - } } void setLastErrorAndTransitionToInitialState(int error) { diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java index 3455afa9fedf..9306080aae1c 100644 --- a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java +++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java @@ -92,10 +92,7 @@ public class TetherInterfaceSMTest { public void shouldDoNothingUntilRequested() { initStateMachine(false); final int [] NOOP_COMMANDS = { - TetherInterfaceSM.CMD_TETHER_MODE_DEAD, TetherInterfaceSM.CMD_TETHER_UNREQUESTED, - TetherInterfaceSM.CMD_INTERFACE_UP, - TetherInterfaceSM.CMD_CELL_DUN_ERROR, TetherInterfaceSM.CMD_IP_FORWARDING_ENABLE_ERROR, TetherInterfaceSM.CMD_IP_FORWARDING_DISABLE_ERROR, TetherInterfaceSM.CMD_START_TETHERING_ERROR, -- cgit v1.2.3-59-g8ed1b From 7040b4ebdfc9d4de5c240db6cde9fc245fb7a696 Mon Sep 17 00:00:00 2001 From: Mitchell Wills Date: Mon, 23 May 2016 16:40:10 -0700 Subject: Rename TetherInterfaceSM to TetherInterfaceStateMachine ( cherry-pick of c5baad02503d17a812ee1b44cc817c7e18935ff1 ) Change-Id: I324690b20f37cef6d58872e21b126a87d16f7ec8 --- .../com/android/server/connectivity/Tethering.java | 77 ++-- .../connectivity/tethering/IControlsTethering.java | 4 +- .../connectivity/tethering/TetherInterfaceSM.java | 406 --------------------- .../tethering/TetherInterfaceStateMachine.java | 406 +++++++++++++++++++++ .../tethering/TetherInterfaceSMTest.java | 244 ------------- .../tethering/TetherInterfaceStateMachineTest.java | 245 +++++++++++++ 6 files changed, 692 insertions(+), 690 deletions(-) delete mode 100644 services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java create mode 100644 services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java delete mode 100644 services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java create mode 100644 services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 66e6c7e3d0d7..c26520e5b5d6 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -71,7 +71,7 @@ import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.server.IoThread; import com.android.server.connectivity.tethering.IControlsTethering; -import com.android.server.connectivity.tethering.TetherInterfaceSM; +import com.android.server.connectivity.tethering.TetherInterfaceStateMachine; import com.android.server.net.BaseNetworkObserver; import java.io.FileDescriptor; @@ -101,7 +101,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering private final static boolean VDBG = false; private static final Class[] messageClasses = { - Tethering.class, TetherMasterSM.class, TetherInterfaceSM.class + Tethering.class, TetherMasterSM.class, TetherInterfaceStateMachine.class }; private static final SparseArray sMagicDecoderRing = MessageUtils.findMessageNames(messageClasses); @@ -127,7 +127,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering private final INetworkStatsService mStatsService; private final Looper mLooper; - private HashMap mIfaces; // all tethered/tetherable ifaces + private HashMap mIfaces; // all tethered/tetherable ifaces private BroadcastReceiver mStateReceiver; @@ -172,7 +172,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering mPublicSync = new Object(); - mIfaces = new HashMap(); + mIfaces = new HashMap(); // make our own thread so we don't anr the system mLooper = IoThread.get().getLooper(); @@ -259,10 +259,10 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } if (found == false) return; - TetherInterfaceSM sm = mIfaces.get(iface); + TetherInterfaceStateMachine sm = mIfaces.get(iface); if (up) { if (sm == null) { - sm = new TetherInterfaceSM(iface, mLooper, usb, mPublicSync, + sm = new TetherInterfaceStateMachine(iface, mLooper, usb, mPublicSync, mNMService, mStatsService, this); mIfaces.put(iface, sm); sm.start(); @@ -273,7 +273,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering // we will handle disconnect in interfaceRemoved instead if (VDBG) Log.d(TAG, "ignore interface down for " + iface); } else if (sm != null) { - sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN); + sm.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); mIfaces.remove(iface); } } @@ -333,12 +333,12 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering return; } - TetherInterfaceSM sm = mIfaces.get(iface); + TetherInterfaceStateMachine sm = mIfaces.get(iface); if (sm != null) { if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring"); return; } - sm = new TetherInterfaceSM(iface, mLooper, usb, mPublicSync, + sm = new TetherInterfaceStateMachine(iface, mLooper, usb, mPublicSync, mNMService, mStatsService, this); mIfaces.put(iface, sm); sm.start(); @@ -349,14 +349,14 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering public void interfaceRemoved(String iface) { if (VDBG) Log.d(TAG, "interfaceRemoved " + iface); synchronized (mPublicSync) { - TetherInterfaceSM sm = mIfaces.get(iface); + TetherInterfaceStateMachine sm = mIfaces.get(iface); if (sm == null) { if (VDBG) { Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring"); } return; } - sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN); + sm.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); mIfaces.remove(iface); } } @@ -576,7 +576,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering public int tether(String iface) { if (DBG) Log.d(TAG, "Tethering " + iface); - TetherInterfaceSM sm = null; + TetherInterfaceStateMachine sm = null; synchronized (mPublicSync) { sm = mIfaces.get(iface); } @@ -588,13 +588,13 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring"); return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; } - sm.sendMessage(TetherInterfaceSM.CMD_TETHER_REQUESTED); + sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); return ConnectivityManager.TETHER_ERROR_NO_ERROR; } public int untether(String iface) { if (DBG) Log.d(TAG, "Untethering " + iface); - TetherInterfaceSM sm = null; + TetherInterfaceStateMachine sm = null; synchronized (mPublicSync) { sm = mIfaces.get(iface); } @@ -606,7 +606,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering Log.e(TAG, "Tried to Untethered an errored iface :" + iface + ", ignoring"); return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; } - sm.sendMessage(TetherInterfaceSM.CMD_TETHER_UNREQUESTED); + sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); return ConnectivityManager.TETHER_ERROR_NO_ERROR; } @@ -618,7 +618,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } public int getLastTetherError(String iface) { - TetherInterfaceSM sm = null; + TetherInterfaceStateMachine sm = null; synchronized (mPublicSync) { sm = mIfaces.get(iface); if (sm == null) { @@ -647,7 +647,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering synchronized (mPublicSync) { Set ifaces = mIfaces.keySet(); for (String iface : ifaces) { - TetherInterfaceSM sm = mIfaces.get(iface); + TetherInterfaceStateMachine sm = mIfaces.get(iface); if (sm != null) { if (sm.isErrored()) { erroredList.add(iface); @@ -917,7 +917,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering synchronized (mPublicSync) { Set keys = mIfaces.keySet(); for (String key : keys) { - TetherInterfaceSM sm = mIfaces.get(key); + TetherInterfaceStateMachine sm = mIfaces.get(key); if (sm.isTethered()) { list.add(key); } @@ -935,7 +935,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering synchronized (mPublicSync) { Set keys = mIfaces.keySet(); for (String key : keys) { - TetherInterfaceSM sm = mIfaces.get(key); + TetherInterfaceStateMachine sm = mIfaces.get(key); if (sm.isAvailable()) { list.add(key); } @@ -957,7 +957,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering synchronized (mPublicSync) { Set keys = mIfaces.keySet(); for (String key : keys) { - TetherInterfaceSM sm = mIfaces.get(key); + TetherInterfaceStateMachine sm = mIfaces.get(key); if (sm.isErrored()) { list.add(key); } @@ -1097,7 +1097,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering private State mStopTetheringErrorState; private State mSetDnsForwardersErrorState; - private ArrayList mNotifyList; + private ArrayList mNotifyList; private int mMobileApnReserved = ConnectivityManager.TYPE_NONE; private NetworkCallback mMobileUpstreamCallback; @@ -1124,7 +1124,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering mSetDnsForwardersErrorState = new SetDnsForwardersErrorState(); addState(mSetDnsForwardersErrorState); - mNotifyList = new ArrayList(); + mNotifyList = new ArrayList<>(); setInitialState(mInitialState); } @@ -1339,8 +1339,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering protected void notifyTetheredOfNewUpstreamIface(String ifaceName) { if (DBG) Log.d(TAG, "Notifying tethered with upstream=" + ifaceName); mCurrentUpstreamIface = ifaceName; - for (TetherInterfaceSM sm : mNotifyList) { - sm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED, + for (TetherInterfaceStateMachine sm : mNotifyList) { + sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, ifaceName); } } @@ -1409,7 +1409,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering synchronized (mPublicSync) { Set ifaces = mIfaces.keySet(); for (String iface : ifaces) { - TetherInterfaceSM sm = mIfaces.get(iface); + TetherInterfaceStateMachine sm = mIfaces.get(iface); if (sm != null && sm.isTethered()) { if (isUsb(iface)) { tethered.add(new Integer( @@ -1455,13 +1455,13 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering boolean retValue = true; switch (message.what) { case CMD_TETHER_MODE_REQUESTED: - TetherInterfaceSM who = (TetherInterfaceSM)message.obj; + TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj; if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); mNotifyList.add(who); transitionTo(mTetherModeAliveState); break; case CMD_TETHER_MODE_UNREQUESTED: - who = (TetherInterfaceSM)message.obj; + who = (TetherInterfaceStateMachine)message.obj; if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who); int index = mNotifyList.indexOf(who); if (index != -1) { @@ -1503,14 +1503,14 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering boolean retValue = true; switch (message.what) { case CMD_TETHER_MODE_REQUESTED: - TetherInterfaceSM who = (TetherInterfaceSM)message.obj; + TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj; if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); mNotifyList.add(who); - who.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED, + who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, mCurrentUpstreamIface); break; case CMD_TETHER_MODE_UNREQUESTED: - who = (TetherInterfaceSM)message.obj; + who = (TetherInterfaceStateMachine)message.obj; if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who); int index = mNotifyList.indexOf(who); if (index != -1) { @@ -1572,7 +1572,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering boolean retValue = true; switch (message.what) { case CMD_TETHER_MODE_REQUESTED: - TetherInterfaceSM who = (TetherInterfaceSM)message.obj; + TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj; who.sendMessage(mErrorNotification); break; default: @@ -1583,7 +1583,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering void notify(int msgType) { mErrorNotification = msgType; for (Object o : mNotifyList) { - TetherInterfaceSM sm = (TetherInterfaceSM)o; + TetherInterfaceStateMachine sm = (TetherInterfaceStateMachine)o; sm.sendMessage(msgType); } } @@ -1593,7 +1593,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering @Override public void enter() { Log.e(TAG, "Error in setIpForwardingEnabled"); - notify(TetherInterfaceSM.CMD_IP_FORWARDING_ENABLE_ERROR); + notify(TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR); } } @@ -1601,7 +1601,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering @Override public void enter() { Log.e(TAG, "Error in setIpForwardingDisabled"); - notify(TetherInterfaceSM.CMD_IP_FORWARDING_DISABLE_ERROR); + notify(TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR); } } @@ -1609,7 +1609,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering @Override public void enter() { Log.e(TAG, "Error in startTethering"); - notify(TetherInterfaceSM.CMD_START_TETHERING_ERROR); + notify(TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR); try { mNMService.setIpForwardingEnabled(false); } catch (Exception e) {} @@ -1620,7 +1620,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering @Override public void enter() { Log.e(TAG, "Error in stopTethering"); - notify(TetherInterfaceSM.CMD_STOP_TETHERING_ERROR); + notify(TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR); try { mNMService.setIpForwardingEnabled(false); } catch (Exception e) {} @@ -1631,7 +1631,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering @Override public void enter() { Log.e(TAG, "Error in setDnsForwarders"); - notify(TetherInterfaceSM.CMD_SET_DNS_FORWARDERS_ERROR); + notify(TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR); try { mNMService.stopTethering(); } catch (Exception e) {} @@ -1675,7 +1675,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } @Override - public void notifyInterfaceTetheringReadiness(boolean isReady, TetherInterfaceSM who) { + public void notifyInterfaceTetheringReadiness(boolean isReady, + TetherInterfaceStateMachine who) { mTetherMasterSM.sendMessage((isReady) ? TetherMasterSM.CMD_TETHER_MODE_REQUESTED : TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, who); } diff --git a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java index 9f4effff29a8..7677daf808e8 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java +++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java @@ -23,5 +23,5 @@ package com.android.server.connectivity.tethering; */ public interface IControlsTethering { void sendTetherStateChangedBroadcast(); - void notifyInterfaceTetheringReadiness(boolean isReady, TetherInterfaceSM who); -} \ No newline at end of file + void notifyInterfaceTetheringReadiness(boolean isReady, TetherInterfaceStateMachine who); +} diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java deleted file mode 100644 index cc852d6ea510..000000000000 --- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceSM.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * 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.tethering; - -import android.net.ConnectivityManager; -import android.net.INetworkStatsService; -import android.net.InterfaceConfiguration; -import android.net.LinkAddress; -import android.net.NetworkUtils; -import android.os.INetworkManagementService; -import android.os.Looper; -import android.os.Message; -import android.util.Log; -import android.util.SparseArray; - -import com.android.internal.util.IState; -import com.android.internal.util.MessageUtils; -import com.android.internal.util.Protocol; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; - -import java.net.InetAddress; - -/** - * @hide - * - * Tracks the eligibility of a given network interface for tethering. - */ -public class TetherInterfaceSM extends StateMachine { - private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129"; - private static final int USB_PREFIX_LENGTH = 24; - - private final static String TAG = "TetherInterfaceSM"; - private final static boolean DBG = false; - private final static boolean VDBG = false; - private static final Class[] messageClasses = { - TetherInterfaceSM.class - }; - private static final SparseArray sMagicDecoderRing = - MessageUtils.findMessageNames(messageClasses); - - private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100; - // request from the user that it wants to tether - public static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2; - // request from the user that it wants to untether - public static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3; - // notification that this interface is down - public static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4; - // notification from the master SM that it had trouble enabling IP Forwarding - public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7; - // notification from the master SM that it had trouble disabling IP Forwarding - public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8; - // notification from the master SM that it had trouble starting tethering - public static final int CMD_START_TETHERING_ERROR = BASE_IFACE + 9; - // notification from the master SM that it had trouble stopping tethering - public static final int CMD_STOP_TETHERING_ERROR = BASE_IFACE + 10; - // notification from the master SM that it had trouble setting the DNS forwarders - public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IFACE + 11; - // the upstream connection has changed - public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12; - - private final State mInitialState; - private final State mTetheredState; - private final State mUnavailableState; - - private final INetworkManagementService mNMService; - private final INetworkStatsService mStatsService; - private final IControlsTethering mTetherController; - - private final boolean mUsb; - private final String mIfaceName; - - private final Object mMutex; // Protects the fields below. - private boolean mAvailable; - private boolean mTethered; - private int mLastError; - private String mMyUpstreamIfaceName; // may change over time - - public TetherInterfaceSM(String ifaceName, Looper looper, boolean usb, Object mutex, - INetworkManagementService nMService, INetworkStatsService statsService, - IControlsTethering tetherController) { - super(ifaceName, looper); - mNMService = nMService; - mStatsService = statsService; - mTetherController = tetherController; - mIfaceName = ifaceName; - mUsb = usb; - mMutex = mutex; - setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); - - mInitialState = new InitialState(); - addState(mInitialState); - mTetheredState = new TetheredState(); - addState(mTetheredState); - mUnavailableState = new UnavailableState(); - addState(mUnavailableState); - - setInitialState(mInitialState); - } - - @Override - public String toString() { - String res = new String(); - res += mIfaceName + " - "; - IState current = getCurrentState(); - if (current == mInitialState) res += "InitialState"; - if (current == mTetheredState) res += "TetheredState"; - if (current == mUnavailableState) res += "UnavailableState"; - if (isAvailable()) res += " - Available"; - if (isTethered()) res += " - Tethered"; - res += " - lastError =" + getLastError(); - return res; - } - - public int getLastError() { - synchronized (mMutex) { - return mLastError; - } - } - - private void setLastError(int error) { - synchronized (mMutex) { - mLastError = error; - - if (isErrored()) { - if (mUsb) { - // note everything's been unwound by this point so nothing to do on - // further error.. - configureUsbIface(false, mIfaceName); - } - } - } - } - - public boolean isAvailable() { - synchronized (mMutex) { - return mAvailable; - } - } - - private void setAvailable(boolean available) { - synchronized (mMutex) { - mAvailable = available; - } - } - - public boolean isTethered() { - synchronized (mMutex) { - return mTethered; - } - } - - private void setTethered(boolean tethered) { - synchronized (mMutex) { - mTethered = tethered; - } - } - - public boolean isErrored() { - synchronized (mMutex) { - return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR); - } - } - - // configured when we start tethering and unconfig'd on error or conclusion - private boolean configureUsbIface(boolean enabled, String iface) { - if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); - - InterfaceConfiguration ifcg = null; - try { - ifcg = mNMService.getInterfaceConfig(iface); - if (ifcg != null) { - InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); - ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH)); - if (enabled) { - ifcg.setInterfaceUp(); - } else { - ifcg.setInterfaceDown(); - } - ifcg.clearFlag("running"); - mNMService.setInterfaceConfig(iface, ifcg); - } - } catch (Exception e) { - Log.e(TAG, "Error configuring interface " + iface, e); - return false; - } - - return true; - } - - private void maybeLogMessage(State state, int what) { - if (DBG) { - Log.d(TAG, state.getName() + " got " + - sMagicDecoderRing.get(what, Integer.toString(what))); - } - } - - class InitialState extends State { - @Override - public void enter() { - setAvailable(true); - setTethered(false); - mTetherController.sendTetherStateChangedBroadcast(); - } - - @Override - public boolean processMessage(Message message) { - maybeLogMessage(this, message.what); - boolean retValue = true; - switch (message.what) { - case CMD_TETHER_REQUESTED: - setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); - mTetherController.notifyInterfaceTetheringReadiness(true, TetherInterfaceSM.this); - transitionTo(mTetheredState); - break; - case CMD_INTERFACE_DOWN: - transitionTo(mUnavailableState); - break; - default: - retValue = false; - break; - } - return retValue; - } - } - - class TetheredState extends State { - @Override - public void enter() { - setAvailable(false); - if (mUsb) { - if (!configureUsbIface(true, mIfaceName)) { - mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); - setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - - transitionTo(mInitialState); - return; - } - } - - try { - mNMService.tetherInterface(mIfaceName); - } catch (Exception e) { - Log.e(TAG, "Error Tethering: " + e.toString()); - setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR); - - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception ee) { - Log.e(TAG, "Error untethering after failure!" + ee.toString()); - } - transitionTo(mInitialState); - return; - } - if (DBG) Log.d(TAG, "Tethered " + mIfaceName); - setTethered(true); - mTetherController.sendTetherStateChangedBroadcast(); - } - - private void cleanupUpstream() { - if (mMyUpstreamIfaceName != null) { - // note that we don't care about errors here. - // sometimes interfaces are gone before we get - // to remove their rules, which generates errors. - // just do the best we can. - try { - // about to tear down NAT; gather remaining statistics - mStatsService.forceUpdate(); - } catch (Exception e) { - if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString()); - } - try { - mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName); - } catch (Exception e) { - if (VDBG) Log.e( - TAG, "Exception in removeInterfaceForward: " + e.toString()); - } - try { - mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName); - } catch (Exception e) { - if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString()); - } - mMyUpstreamIfaceName = null; - } - return; - } - - @Override - public boolean processMessage(Message message) { - maybeLogMessage(this, message.what); - boolean retValue = true; - switch (message.what) { - case CMD_TETHER_UNREQUESTED: - case CMD_INTERFACE_DOWN: - cleanupUpstream(); - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception e) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); - break; - } - mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceSM.this); - if (message.what == CMD_TETHER_UNREQUESTED) { - if (mUsb) { - if (!configureUsbIface(false, mIfaceName)) { - setLastError( - ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - } - } - transitionTo(mInitialState); - } else if (message.what == CMD_INTERFACE_DOWN) { - transitionTo(mUnavailableState); - } - if (DBG) Log.d(TAG, "Untethered " + mIfaceName); - break; - case CMD_TETHER_CONNECTION_CHANGED: - String newUpstreamIfaceName = (String)(message.obj); - if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) || - (mMyUpstreamIfaceName != null && - mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) { - if (VDBG) Log.d(TAG, "Connection changed noop - dropping"); - break; - } - cleanupUpstream(); - if (newUpstreamIfaceName != null) { - try { - mNMService.enableNat(mIfaceName, newUpstreamIfaceName); - mNMService.startInterfaceForwarding(mIfaceName, - newUpstreamIfaceName); - } catch (Exception e) { - Log.e(TAG, "Exception enabling Nat: " + e.toString()); - try { - mNMService.disableNat(mIfaceName, newUpstreamIfaceName); - } catch (Exception ee) {} - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception ee) {} - - setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR); - transitionTo(mInitialState); - return true; - } - } - mMyUpstreamIfaceName = newUpstreamIfaceName; - break; - case CMD_IP_FORWARDING_ENABLE_ERROR: - case CMD_IP_FORWARDING_DISABLE_ERROR: - case CMD_START_TETHERING_ERROR: - case CMD_STOP_TETHERING_ERROR: - case CMD_SET_DNS_FORWARDERS_ERROR: - cleanupUpstream(); - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception e) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); - break; - } - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_MASTER_ERROR); - break; - default: - retValue = false; - break; - } - return retValue; - } - } - - /** - * This state is terminal for the per interface state machine. At this - * point, the master state machine should have removed this interface - * specific state machine from its list of possible recipients of - * tethering requests. The state machine itself will hang around until - * the garbage collector finds it. - */ - class UnavailableState extends State { - @Override - public void enter() { - setAvailable(false); - setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); - setTethered(false); - mTetherController.sendTetherStateChangedBroadcast(); - } - } - - void setLastErrorAndTransitionToInitialState(int error) { - setLastError(error); - transitionTo(mInitialState); - } -} diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java new file mode 100644 index 000000000000..875fc60bc326 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java @@ -0,0 +1,406 @@ +/* + * 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.tethering; + +import android.net.ConnectivityManager; +import android.net.INetworkStatsService; +import android.net.InterfaceConfiguration; +import android.net.LinkAddress; +import android.net.NetworkUtils; +import android.os.INetworkManagementService; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.util.IState; +import com.android.internal.util.MessageUtils; +import com.android.internal.util.Protocol; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.net.InetAddress; + +/** + * @hide + * + * Tracks the eligibility of a given network interface for tethering. + */ +public class TetherInterfaceStateMachine extends StateMachine { + private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129"; + private static final int USB_PREFIX_LENGTH = 24; + + private final static String TAG = "TetherInterfaceSM"; + private final static boolean DBG = false; + private final static boolean VDBG = false; + private static final Class[] messageClasses = { + TetherInterfaceStateMachine.class + }; + private static final SparseArray sMagicDecoderRing = + MessageUtils.findMessageNames(messageClasses); + + private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100; + // request from the user that it wants to tether + public static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2; + // request from the user that it wants to untether + public static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3; + // notification that this interface is down + public static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4; + // notification from the master SM that it had trouble enabling IP Forwarding + public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7; + // notification from the master SM that it had trouble disabling IP Forwarding + public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8; + // notification from the master SM that it had trouble starting tethering + public static final int CMD_START_TETHERING_ERROR = BASE_IFACE + 9; + // notification from the master SM that it had trouble stopping tethering + public static final int CMD_STOP_TETHERING_ERROR = BASE_IFACE + 10; + // notification from the master SM that it had trouble setting the DNS forwarders + public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IFACE + 11; + // the upstream connection has changed + public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12; + + private final State mInitialState; + private final State mTetheredState; + private final State mUnavailableState; + + private final INetworkManagementService mNMService; + private final INetworkStatsService mStatsService; + private final IControlsTethering mTetherController; + + private final boolean mUsb; + private final String mIfaceName; + + private final Object mMutex; // Protects the fields below. + private boolean mAvailable; + private boolean mTethered; + private int mLastError; + private String mMyUpstreamIfaceName; // may change over time + + public TetherInterfaceStateMachine(String ifaceName, Looper looper, boolean usb, Object mutex, + INetworkManagementService nMService, INetworkStatsService statsService, + IControlsTethering tetherController) { + super(ifaceName, looper); + mNMService = nMService; + mStatsService = statsService; + mTetherController = tetherController; + mIfaceName = ifaceName; + mUsb = usb; + mMutex = mutex; + setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); + + mInitialState = new InitialState(); + addState(mInitialState); + mTetheredState = new TetheredState(); + addState(mTetheredState); + mUnavailableState = new UnavailableState(); + addState(mUnavailableState); + + setInitialState(mInitialState); + } + + @Override + public String toString() { + String res = new String(); + res += mIfaceName + " - "; + IState current = getCurrentState(); + if (current == mInitialState) res += "InitialState"; + if (current == mTetheredState) res += "TetheredState"; + if (current == mUnavailableState) res += "UnavailableState"; + if (isAvailable()) res += " - Available"; + if (isTethered()) res += " - Tethered"; + res += " - lastError =" + getLastError(); + return res; + } + + public int getLastError() { + synchronized (mMutex) { + return mLastError; + } + } + + private void setLastError(int error) { + synchronized (mMutex) { + mLastError = error; + + if (isErrored()) { + if (mUsb) { + // note everything's been unwound by this point so nothing to do on + // further error.. + configureUsbIface(false, mIfaceName); + } + } + } + } + + public boolean isAvailable() { + synchronized (mMutex) { + return mAvailable; + } + } + + private void setAvailable(boolean available) { + synchronized (mMutex) { + mAvailable = available; + } + } + + public boolean isTethered() { + synchronized (mMutex) { + return mTethered; + } + } + + private void setTethered(boolean tethered) { + synchronized (mMutex) { + mTethered = tethered; + } + } + + public boolean isErrored() { + synchronized (mMutex) { + return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR); + } + } + + // configured when we start tethering and unconfig'd on error or conclusion + private boolean configureUsbIface(boolean enabled, String iface) { + if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); + + InterfaceConfiguration ifcg = null; + try { + ifcg = mNMService.getInterfaceConfig(iface); + if (ifcg != null) { + InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); + ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH)); + if (enabled) { + ifcg.setInterfaceUp(); + } else { + ifcg.setInterfaceDown(); + } + ifcg.clearFlag("running"); + mNMService.setInterfaceConfig(iface, ifcg); + } + } catch (Exception e) { + Log.e(TAG, "Error configuring interface " + iface, e); + return false; + } + + return true; + } + + private void maybeLogMessage(State state, int what) { + if (DBG) { + Log.d(TAG, state.getName() + " got " + + sMagicDecoderRing.get(what, Integer.toString(what))); + } + } + + class InitialState extends State { + @Override + public void enter() { + setAvailable(true); + setTethered(false); + mTetherController.sendTetherStateChangedBroadcast(); + } + + @Override + public boolean processMessage(Message message) { + maybeLogMessage(this, message.what); + boolean retValue = true; + switch (message.what) { + case CMD_TETHER_REQUESTED: + setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); + mTetherController.notifyInterfaceTetheringReadiness(true, TetherInterfaceStateMachine.this); + transitionTo(mTetheredState); + break; + case CMD_INTERFACE_DOWN: + transitionTo(mUnavailableState); + break; + default: + retValue = false; + break; + } + return retValue; + } + } + + class TetheredState extends State { + @Override + public void enter() { + setAvailable(false); + if (mUsb) { + if (!configureUsbIface(true, mIfaceName)) { + mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceStateMachine.this); + setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); + + transitionTo(mInitialState); + return; + } + } + + try { + mNMService.tetherInterface(mIfaceName); + } catch (Exception e) { + Log.e(TAG, "Error Tethering: " + e.toString()); + setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR); + + try { + mNMService.untetherInterface(mIfaceName); + } catch (Exception ee) { + Log.e(TAG, "Error untethering after failure!" + ee.toString()); + } + transitionTo(mInitialState); + return; + } + if (DBG) Log.d(TAG, "Tethered " + mIfaceName); + setTethered(true); + mTetherController.sendTetherStateChangedBroadcast(); + } + + private void cleanupUpstream() { + if (mMyUpstreamIfaceName != null) { + // note that we don't care about errors here. + // sometimes interfaces are gone before we get + // to remove their rules, which generates errors. + // just do the best we can. + try { + // about to tear down NAT; gather remaining statistics + mStatsService.forceUpdate(); + } catch (Exception e) { + if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString()); + } + try { + mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName); + } catch (Exception e) { + if (VDBG) Log.e( + TAG, "Exception in removeInterfaceForward: " + e.toString()); + } + try { + mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName); + } catch (Exception e) { + if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString()); + } + mMyUpstreamIfaceName = null; + } + return; + } + + @Override + public boolean processMessage(Message message) { + maybeLogMessage(this, message.what); + boolean retValue = true; + switch (message.what) { + case CMD_TETHER_UNREQUESTED: + case CMD_INTERFACE_DOWN: + cleanupUpstream(); + try { + mNMService.untetherInterface(mIfaceName); + } catch (Exception e) { + setLastErrorAndTransitionToInitialState( + ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); + break; + } + mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceStateMachine.this); + if (message.what == CMD_TETHER_UNREQUESTED) { + if (mUsb) { + if (!configureUsbIface(false, mIfaceName)) { + setLastError( + ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); + } + } + transitionTo(mInitialState); + } else if (message.what == CMD_INTERFACE_DOWN) { + transitionTo(mUnavailableState); + } + if (DBG) Log.d(TAG, "Untethered " + mIfaceName); + break; + case CMD_TETHER_CONNECTION_CHANGED: + String newUpstreamIfaceName = (String)(message.obj); + if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) || + (mMyUpstreamIfaceName != null && + mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) { + if (VDBG) Log.d(TAG, "Connection changed noop - dropping"); + break; + } + cleanupUpstream(); + if (newUpstreamIfaceName != null) { + try { + mNMService.enableNat(mIfaceName, newUpstreamIfaceName); + mNMService.startInterfaceForwarding(mIfaceName, + newUpstreamIfaceName); + } catch (Exception e) { + Log.e(TAG, "Exception enabling Nat: " + e.toString()); + try { + mNMService.disableNat(mIfaceName, newUpstreamIfaceName); + } catch (Exception ee) {} + try { + mNMService.untetherInterface(mIfaceName); + } catch (Exception ee) {} + + setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR); + transitionTo(mInitialState); + return true; + } + } + mMyUpstreamIfaceName = newUpstreamIfaceName; + break; + case CMD_IP_FORWARDING_ENABLE_ERROR: + case CMD_IP_FORWARDING_DISABLE_ERROR: + case CMD_START_TETHERING_ERROR: + case CMD_STOP_TETHERING_ERROR: + case CMD_SET_DNS_FORWARDERS_ERROR: + cleanupUpstream(); + try { + mNMService.untetherInterface(mIfaceName); + } catch (Exception e) { + setLastErrorAndTransitionToInitialState( + ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); + break; + } + setLastErrorAndTransitionToInitialState( + ConnectivityManager.TETHER_ERROR_MASTER_ERROR); + break; + default: + retValue = false; + break; + } + return retValue; + } + } + + /** + * This state is terminal for the per interface state machine. At this + * point, the master state machine should have removed this interface + * specific state machine from its list of possible recipients of + * tethering requests. The state machine itself will hang around until + * the garbage collector finds it. + */ + class UnavailableState extends State { + @Override + public void enter() { + setAvailable(false); + setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); + setTethered(false); + mTetherController.sendTetherStateChangedBroadcast(); + } + } + + void setLastErrorAndTransitionToInitialState(int error) { + setLastError(error); + transitionTo(mInitialState); + } +} diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java deleted file mode 100644 index 9306080aae1c..000000000000 --- a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceSMTest.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * 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.tethering; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.net.INetworkStatsService; -import android.net.InterfaceConfiguration; -import android.os.INetworkManagementService; -import android.os.RemoteException; -import android.os.test.TestLooper; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - - -public class TetherInterfaceSMTest { - private static final String IFACE_NAME = "testnet1"; - private static final String UPSTREAM_IFACE = "upstream0"; - private static final String UPSTREAM_IFACE2 = "upstream1"; - - @Mock private INetworkManagementService mNMService; - @Mock private INetworkStatsService mStatsService; - @Mock private IControlsTethering mTetherHelper; - @Mock private InterfaceConfiguration mInterfaceConfiguration; - - private final TestLooper mLooper = new TestLooper(); - private final Object mMutex = new Object(); - private TetherInterfaceSM mTestedSm; - - private void initStateMachine(boolean isUsb) { - mTestedSm = new TetherInterfaceSM(IFACE_NAME, mLooper.getLooper(), isUsb, mMutex, - mNMService, mStatsService, mTetherHelper); - mTestedSm.start(); - // Starting the state machine always puts us in a consistent state and notifies - // the test of the world that we've changed from an unknown to available state. - mLooper.dispatchAll(); - reset(mNMService, mStatsService, mTetherHelper); - } - - private void initTetheredStateMachine(boolean isUsb, String upstreamIface) { - initStateMachine(isUsb); - dispatchCommand(TetherInterfaceSM.CMD_TETHER_REQUESTED); - if (upstreamIface != null) { - dispatchTetherConnectionChanged(upstreamIface); - } - reset(mNMService, mStatsService, mTetherHelper); - } - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - } - - @Test - public void startsOutAvailable() { - mTestedSm = new TetherInterfaceSM(IFACE_NAME, mLooper.getLooper(), false, mMutex, - mNMService, mStatsService, mTetherHelper); - mTestedSm.start(); - mLooper.dispatchAll(); - assertTrue("Should start out available for tethering", mTestedSm.isAvailable()); - assertFalse("Should not be tethered initially", mTestedSm.isTethered()); - assertFalse("Should have no errors initially", mTestedSm.isErrored()); - verify(mTetherHelper).sendTetherStateChangedBroadcast(); - verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService); - } - - @Test - public void shouldDoNothingUntilRequested() { - initStateMachine(false); - final int [] NOOP_COMMANDS = { - TetherInterfaceSM.CMD_TETHER_UNREQUESTED, - TetherInterfaceSM.CMD_IP_FORWARDING_ENABLE_ERROR, - TetherInterfaceSM.CMD_IP_FORWARDING_DISABLE_ERROR, - TetherInterfaceSM.CMD_START_TETHERING_ERROR, - TetherInterfaceSM.CMD_STOP_TETHERING_ERROR, - TetherInterfaceSM.CMD_SET_DNS_FORWARDERS_ERROR, - TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED - }; - for (int command : NOOP_COMMANDS) { - // None of these commands should trigger us to request action from - // the rest of the system. - dispatchCommand(command); - verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); - } - } - - @Test - public void handlesImmediateInterfaceDown() { - initStateMachine(false); - dispatchCommand(TetherInterfaceSM.CMD_INTERFACE_DOWN); - verify(mTetherHelper).sendTetherStateChangedBroadcast(); - verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); - assertFalse("Should not be tetherable when the interface is down", mTestedSm.isAvailable()); - assertFalse("Should not be tethered when the interface is down", mTestedSm.isTethered()); - assertFalse("Should have no errors when the interface goes immediately down", - mTestedSm.isErrored()); - } - - @Test - public void canBeTethered() throws RemoteException { - initStateMachine(false); - dispatchCommand(TetherInterfaceSM.CMD_TETHER_REQUESTED); - InOrder inOrder = inOrder(mTetherHelper, mNMService); - inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); - inOrder.verify(mNMService).tetherInterface(IFACE_NAME); - inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); - - verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); - assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); - assertTrue("Should be in a tethered state", mTestedSm.isTethered()); - assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); - } - - @Test - public void canUnrequestTethering() throws Exception { - initTetheredStateMachine(false, null); - - dispatchCommand(TetherInterfaceSM.CMD_TETHER_UNREQUESTED); - InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); - inOrder.verify(mNMService).untetherInterface(IFACE_NAME); - inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); - inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); - verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); - assertTrue("Should be ready for tethering again", mTestedSm.isAvailable()); - assertFalse("Should not be tethered", mTestedSm.isTethered()); - assertFalse("Should have no errors", mTestedSm.isErrored()); - } - - @Test - public void canBeTetheredAsUsb() throws RemoteException { - initStateMachine(true); - - when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); - dispatchCommand(TetherInterfaceSM.CMD_TETHER_REQUESTED); - - InOrder inOrder = inOrder(mTetherHelper, mNMService); - inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); - inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); - inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); - inOrder.verify(mNMService).tetherInterface(IFACE_NAME); - inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); - - verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); - assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); - assertTrue("Should be in a tethered state", mTestedSm.isTethered()); - assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); - } - - @Test - public void handlesFirstUpstreamChange() throws Exception { - initTetheredStateMachine(false, null); - - // Telling the state machine about its upstream interface triggers a little more configuration. - dispatchTetherConnectionChanged(UPSTREAM_IFACE); - InOrder inOrder = inOrder(mNMService); - inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); - assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); - assertTrue("Should be in a tethered state", mTestedSm.isTethered()); - assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); - } - - @Test - public void handlesChangingUpstream() throws Exception { - initTetheredStateMachine(false, UPSTREAM_IFACE); - - dispatchTetherConnectionChanged(UPSTREAM_IFACE2); - InOrder inOrder = inOrder(mNMService, mStatsService); - inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); - verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); - assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); - assertTrue("Should be in a tethered state", mTestedSm.isTethered()); - assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); - } - - @Test - public void canUnrequestTetheringWithUpstream() throws Exception { - initTetheredStateMachine(false, UPSTREAM_IFACE); - - dispatchCommand(TetherInterfaceSM.CMD_TETHER_UNREQUESTED); - InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); - inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).untetherInterface(IFACE_NAME); - inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); - inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); - verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); - assertTrue("Should be ready for tethering again", mTestedSm.isAvailable()); - assertFalse("Should not be tethered", mTestedSm.isTethered()); - assertFalse("Should have no errors", mTestedSm.isErrored()); - } - - - /** - * Send a command to the state machine under test, and run the event loop to idle. - * - * @param command One of the TetherInterfaceSM.CMD_* constants. - */ - private void dispatchCommand(int command) { - mTestedSm.sendMessage(command); - mLooper.dispatchAll(); - } - - /** - * Special override to tell the state machine that the upstream interface has changed. - * - * @see #dispatchCommand(int) - * @param upstreamIface String name of upstream interface (or null) - */ - private void dispatchTetherConnectionChanged(String upstreamIface) { - mTestedSm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED, upstreamIface); - mLooper.dispatchAll(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java new file mode 100644 index 000000000000..f7cc1da05a83 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java @@ -0,0 +1,245 @@ +/* + * 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.tethering; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.net.INetworkStatsService; +import android.net.InterfaceConfiguration; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.test.TestLooper; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + + +public class TetherInterfaceStateMachineTest { + private static final String IFACE_NAME = "testnet1"; + private static final String UPSTREAM_IFACE = "upstream0"; + private static final String UPSTREAM_IFACE2 = "upstream1"; + + @Mock private INetworkManagementService mNMService; + @Mock private INetworkStatsService mStatsService; + @Mock private IControlsTethering mTetherHelper; + @Mock private InterfaceConfiguration mInterfaceConfiguration; + + private final TestLooper mLooper = new TestLooper(); + private final Object mMutex = new Object(); + private TetherInterfaceStateMachine mTestedSm; + + private void initStateMachine(boolean isUsb) { + mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), isUsb, mMutex, + mNMService, mStatsService, mTetherHelper); + mTestedSm.start(); + // Starting the state machine always puts us in a consistent state and notifies + // the test of the world that we've changed from an unknown to available state. + mLooper.dispatchAll(); + reset(mNMService, mStatsService, mTetherHelper); + } + + private void initTetheredStateMachine(boolean isUsb, String upstreamIface) { + initStateMachine(isUsb); + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + if (upstreamIface != null) { + dispatchTetherConnectionChanged(upstreamIface); + } + reset(mNMService, mStatsService, mTetherHelper); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void startsOutAvailable() { + mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), false, mMutex, + mNMService, mStatsService, mTetherHelper); + mTestedSm.start(); + mLooper.dispatchAll(); + assertTrue("Should start out available for tethering", mTestedSm.isAvailable()); + assertFalse("Should not be tethered initially", mTestedSm.isTethered()); + assertFalse("Should have no errors initially", mTestedSm.isErrored()); + verify(mTetherHelper).sendTetherStateChangedBroadcast(); + verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService); + } + + @Test + public void shouldDoNothingUntilRequested() { + initStateMachine(false); + final int [] NOOP_COMMANDS = { + TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED, + TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR, + TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR, + TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR, + TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR, + TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR, + TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED + }; + for (int command : NOOP_COMMANDS) { + // None of these commands should trigger us to request action from + // the rest of the system. + dispatchCommand(command); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + } + } + + @Test + public void handlesImmediateInterfaceDown() { + initStateMachine(false); + dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); + verify(mTetherHelper).sendTetherStateChangedBroadcast(); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when the interface is down", mTestedSm.isAvailable()); + assertFalse("Should not be tethered when the interface is down", mTestedSm.isTethered()); + assertFalse("Should have no errors when the interface goes immediately down", + mTestedSm.isErrored()); + } + + @Test + public void canBeTethered() throws RemoteException { + initStateMachine(false); + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + InOrder inOrder = inOrder(mTetherHelper, mNMService); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); + inOrder.verify(mNMService).tetherInterface(IFACE_NAME); + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); + assertTrue("Should be in a tethered state", mTestedSm.isTethered()); + assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); + } + + @Test + public void canUnrequestTethering() throws Exception { + initTetheredStateMachine(false, null); + + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); + InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); + inOrder.verify(mNMService).untetherInterface(IFACE_NAME); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertTrue("Should be ready for tethering again", mTestedSm.isAvailable()); + assertFalse("Should not be tethered", mTestedSm.isTethered()); + assertFalse("Should have no errors", mTestedSm.isErrored()); + } + + @Test + public void canBeTetheredAsUsb() throws RemoteException { + initStateMachine(true); + + when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + + InOrder inOrder = inOrder(mTetherHelper, mNMService); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); + inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); + inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); + inOrder.verify(mNMService).tetherInterface(IFACE_NAME); + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); + assertTrue("Should be in a tethered state", mTestedSm.isTethered()); + assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); + } + + @Test + public void handlesFirstUpstreamChange() throws Exception { + initTetheredStateMachine(false, null); + + // Telling the state machine about its upstream interface triggers a little more configuration. + dispatchTetherConnectionChanged(UPSTREAM_IFACE); + InOrder inOrder = inOrder(mNMService); + inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); + assertTrue("Should be in a tethered state", mTestedSm.isTethered()); + assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); + } + + @Test + public void handlesChangingUpstream() throws Exception { + initTetheredStateMachine(false, UPSTREAM_IFACE); + + dispatchTetherConnectionChanged(UPSTREAM_IFACE2); + InOrder inOrder = inOrder(mNMService, mStatsService); + inOrder.verify(mStatsService).forceUpdate(); + inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); + inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); + assertTrue("Should be in a tethered state", mTestedSm.isTethered()); + assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); + } + + @Test + public void canUnrequestTetheringWithUpstream() throws Exception { + initTetheredStateMachine(false, UPSTREAM_IFACE); + + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); + InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); + inOrder.verify(mStatsService).forceUpdate(); + inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).untetherInterface(IFACE_NAME); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertTrue("Should be ready for tethering again", mTestedSm.isAvailable()); + assertFalse("Should not be tethered", mTestedSm.isTethered()); + assertFalse("Should have no errors", mTestedSm.isErrored()); + } + + + /** + * Send a command to the state machine under test, and run the event loop to idle. + * + * @param command One of the TetherInterfaceStateMachine.CMD_* constants. + */ + private void dispatchCommand(int command) { + mTestedSm.sendMessage(command); + mLooper.dispatchAll(); + } + + /** + * Special override to tell the state machine that the upstream interface has changed. + * + * @see #dispatchCommand(int) + * @param upstreamIface String name of upstream interface (or null) + */ + private void dispatchTetherConnectionChanged(String upstreamIface) { + mTestedSm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, + upstreamIface); + mLooper.dispatchAll(); + } +} -- cgit v1.2.3-59-g8ed1b From 4312a4c433611343e863a5d38497d982ccfc90ae Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Mon, 23 May 2016 13:58:00 -0700 Subject: Rely on Tethering mutex for TetherInterfaceSM ( cherry-pick of 4bc8d6b1ac0cb7d4192c829a44d988516f0adbdb ) Stop passing Tethering's mutex into TetherInterfaceSM, and instead simply rely on Tethering to acquire its own mutex when accessing instances of TetherInterfaceSM. While here, remove some boolean fields (previously guarded by the mutex) which duplicate state information. Change-Id: Ie6b2128970981786a5d64fbf700c9f3d0a6721fb Test: Compiles, unittests pass, WiFi tethering continues to work. Bug: 28910007 --- .../com/android/server/connectivity/Tethering.java | 56 +++++++++++----------- .../tethering/TetherInterfaceStateMachine.java | 56 +++++----------------- .../tethering/TetherInterfaceStateMachineTest.java | 5 +- 3 files changed, 42 insertions(+), 75 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index c26520e5b5d6..05823b5f4295 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -262,7 +262,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering TetherInterfaceStateMachine sm = mIfaces.get(iface); if (up) { if (sm == null) { - sm = new TetherInterfaceStateMachine(iface, mLooper, usb, mPublicSync, + sm = new TetherInterfaceStateMachine(iface, mLooper, usb, mNMService, mStatsService, this); mIfaces.put(iface, sm); sm.start(); @@ -338,7 +338,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring"); return; } - sm = new TetherInterfaceStateMachine(iface, mLooper, usb, mPublicSync, + sm = new TetherInterfaceStateMachine(iface, mLooper, usb, mNMService, mStatsService, this); mIfaces.put(iface, sm); sm.start(); @@ -576,38 +576,39 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering public int tether(String iface) { if (DBG) Log.d(TAG, "Tethering " + iface); - TetherInterfaceStateMachine sm = null; synchronized (mPublicSync) { - sm = mIfaces.get(iface); - } - if (sm == null) { - Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring"); - return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; - } - if (!sm.isAvailable() && !sm.isErrored()) { - Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring"); - return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; + TetherInterfaceStateMachine sm = mIfaces.get(iface); + if (sm == null) { + Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring"); + return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; + } + // Ignore the error status of the interface. If the interface is available, + // the errors are referring to past tethering attempts anyway. + if (!sm.isAvailable()) { + Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring"); + return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; + + } + sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + return ConnectivityManager.TETHER_ERROR_NO_ERROR; } - sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); - return ConnectivityManager.TETHER_ERROR_NO_ERROR; } public int untether(String iface) { if (DBG) Log.d(TAG, "Untethering " + iface); - TetherInterfaceStateMachine sm = null; synchronized (mPublicSync) { - sm = mIfaces.get(iface); - } - if (sm == null) { - Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring"); - return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; - } - if (sm.isErrored()) { - Log.e(TAG, "Tried to Untethered an errored iface :" + iface + ", ignoring"); - return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; + TetherInterfaceStateMachine sm = mIfaces.get(iface); + if (sm == null) { + Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring"); + return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; + } + if (!sm.isTethered()) { + Log.e(TAG, "Tried to Untethered an errored iface :" + iface + ", ignoring"); + return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; + } + sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); + return ConnectivityManager.TETHER_ERROR_NO_ERROR; } - sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); - return ConnectivityManager.TETHER_ERROR_NO_ERROR; } public void untetherAll() { @@ -618,9 +619,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } public int getLastTetherError(String iface) { - TetherInterfaceStateMachine sm = null; synchronized (mPublicSync) { - sm = mIfaces.get(iface); + TetherInterfaceStateMachine sm = mIfaces.get(iface); if (sm == null) { Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface + ", ignoring"); diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java index 875fc60bc326..0dc910db94a1 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java @@ -84,13 +84,10 @@ public class TetherInterfaceStateMachine extends StateMachine { private final boolean mUsb; private final String mIfaceName; - private final Object mMutex; // Protects the fields below. - private boolean mAvailable; - private boolean mTethered; private int mLastError; private String mMyUpstreamIfaceName; // may change over time - public TetherInterfaceStateMachine(String ifaceName, Looper looper, boolean usb, Object mutex, + public TetherInterfaceStateMachine(String ifaceName, Looper looper, boolean usb, INetworkManagementService nMService, INetworkStatsService statsService, IControlsTethering tetherController) { super(ifaceName, looper); @@ -99,7 +96,6 @@ public class TetherInterfaceStateMachine extends StateMachine { mTetherController = tetherController; mIfaceName = ifaceName; mUsb = usb; - mMutex = mutex; setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); mInitialState = new InitialState(); @@ -127,53 +123,31 @@ public class TetherInterfaceStateMachine extends StateMachine { } public int getLastError() { - synchronized (mMutex) { - return mLastError; - } + return mLastError; } private void setLastError(int error) { - synchronized (mMutex) { - mLastError = error; - - if (isErrored()) { - if (mUsb) { - // note everything's been unwound by this point so nothing to do on - // further error.. - configureUsbIface(false, mIfaceName); - } + mLastError = error; + + if (isErrored()) { + if (mUsb) { + // note everything's been unwound by this point so nothing to do on + // further error.. + configureUsbIface(false, mIfaceName); } } } public boolean isAvailable() { - synchronized (mMutex) { - return mAvailable; - } - } - - private void setAvailable(boolean available) { - synchronized (mMutex) { - mAvailable = available; - } + return getCurrentState() == mInitialState; } public boolean isTethered() { - synchronized (mMutex) { - return mTethered; - } - } - - private void setTethered(boolean tethered) { - synchronized (mMutex) { - mTethered = tethered; - } + return getCurrentState() == mTetheredState; } public boolean isErrored() { - synchronized (mMutex) { - return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR); - } + return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR); } // configured when we start tethering and unconfig'd on error or conclusion @@ -212,8 +186,6 @@ public class TetherInterfaceStateMachine extends StateMachine { class InitialState extends State { @Override public void enter() { - setAvailable(true); - setTethered(false); mTetherController.sendTetherStateChangedBroadcast(); } @@ -241,7 +213,6 @@ public class TetherInterfaceStateMachine extends StateMachine { class TetheredState extends State { @Override public void enter() { - setAvailable(false); if (mUsb) { if (!configureUsbIface(true, mIfaceName)) { mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceStateMachine.this); @@ -267,7 +238,6 @@ public class TetherInterfaceStateMachine extends StateMachine { return; } if (DBG) Log.d(TAG, "Tethered " + mIfaceName); - setTethered(true); mTetherController.sendTetherStateChangedBroadcast(); } @@ -392,9 +362,7 @@ public class TetherInterfaceStateMachine extends StateMachine { class UnavailableState extends State { @Override public void enter() { - setAvailable(false); setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); - setTethered(false); mTetherController.sendTetherStateChangedBroadcast(); } } diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java index f7cc1da05a83..fb5a1cff2823 100644 --- a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java +++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java @@ -48,11 +48,10 @@ public class TetherInterfaceStateMachineTest { @Mock private InterfaceConfiguration mInterfaceConfiguration; private final TestLooper mLooper = new TestLooper(); - private final Object mMutex = new Object(); private TetherInterfaceStateMachine mTestedSm; private void initStateMachine(boolean isUsb) { - mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), isUsb, mMutex, + mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), isUsb, mNMService, mStatsService, mTetherHelper); mTestedSm.start(); // Starting the state machine always puts us in a consistent state and notifies @@ -77,7 +76,7 @@ public class TetherInterfaceStateMachineTest { @Test public void startsOutAvailable() { - mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), false, mMutex, + mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), false, mNMService, mStatsService, mTetherHelper); mTestedSm.start(); mLooper.dispatchAll(); -- cgit v1.2.3-59-g8ed1b From 0ab0dd317f6f2f22ed894077c1e4268bbfbdf070 Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Wed, 25 May 2016 13:57:27 -0700 Subject: Annotate TetherInterfaceStateMachineTest for APCT ( cherry-pick of d905add4747d6060355c44dac1dce0eccd8014fb ) APCT needs a little additional magic to pick up JUnit4 tests. Bug: 28958508 Test: Tests continue to pass Change-Id: Ia45d91bb3b06e60e3a46ffe738d4468356e066b3 --- .../connectivity/tethering/TetherInterfaceStateMachineTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java index fb5a1cff2823..2c54f3b869f0 100644 --- a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java +++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java @@ -29,14 +29,18 @@ import android.net.InterfaceConfiguration; import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.test.TestLooper; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; - +@RunWith(AndroidJUnit4.class) +@SmallTest public class TetherInterfaceStateMachineTest { private static final String IFACE_NAME = "testnet1"; private static final String UPSTREAM_IFACE = "upstream0"; @@ -241,4 +245,4 @@ public class TetherInterfaceStateMachineTest { upstreamIface); mLooper.dispatchAll(); } -} +} \ No newline at end of file -- cgit v1.2.3-59-g8ed1b From e10bfc0ee67ffe72b75969139b738c66eaa1d66a Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Mon, 23 May 2016 16:17:30 -0700 Subject: Consolidate cleanup logic in TetherInterfaceSM.TetheredState ( cherry-pick of f54c5a932a9ac4a491ce775b21ff8288e40b5bad ) This pushes all TetheredState cleanup logic into a single place. All new unittests fail without the changes to TetherInterfaceSM. Bug: 28915272 Test: Compiles, unittests pass, WiFi tethering continues to work. Change-Id: Ia7bf210e00e9a54f2797baebc2e5333ce314c947 --- .../tethering/TetherInterfaceStateMachine.java | 79 ++++++++-------------- .../tethering/TetherInterfaceStateMachineTest.java | 69 ++++++++++++++++--- 2 files changed, 86 insertions(+), 62 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java index 0dc910db94a1..e33406e67061 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java @@ -128,14 +128,6 @@ public class TetherInterfaceStateMachine extends StateMachine { private void setLastError(int error) { mLastError = error; - - if (isErrored()) { - if (mUsb) { - // note everything's been unwound by this point so nothing to do on - // further error.. - configureUsbIface(false, mIfaceName); - } - } } public boolean isAvailable() { @@ -215,9 +207,7 @@ public class TetherInterfaceStateMachine extends StateMachine { public void enter() { if (mUsb) { if (!configureUsbIface(true, mIfaceName)) { - mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceStateMachine.this); setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - transitionTo(mInitialState); return; } @@ -228,12 +218,6 @@ public class TetherInterfaceStateMachine extends StateMachine { } catch (Exception e) { Log.e(TAG, "Error Tethering: " + e.toString()); setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR); - - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception ee) { - Log.e(TAG, "Error untethering after failure!" + ee.toString()); - } transitionTo(mInitialState); return; } @@ -241,6 +225,28 @@ public class TetherInterfaceStateMachine extends StateMachine { mTetherController.sendTetherStateChangedBroadcast(); } + @Override + public void exit() { + mTetherController.notifyInterfaceTetheringReadiness(false, + TetherInterfaceStateMachine.this); + + // Note that at this point, we're leaving the tethered state. We can fail any + // of these operations, but it doesn't really change that we have to try them + // all in sequence. + cleanupUpstream(); + + try { + mNMService.untetherInterface(mIfaceName); + } catch (Exception ee) { + setLastError(ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); + Log.e(TAG, "Failed to untether interface: " + ee.toString()); + } + + if (mUsb) { + configureUsbIface(false, mIfaceName); + } + } + private void cleanupUpstream() { if (mMyUpstreamIfaceName != null) { // note that we don't care about errors here. @@ -275,28 +281,12 @@ public class TetherInterfaceStateMachine extends StateMachine { boolean retValue = true; switch (message.what) { case CMD_TETHER_UNREQUESTED: + transitionTo(mInitialState); + if (DBG) Log.d(TAG, "Untethered (unrequested)" + mIfaceName); + break; case CMD_INTERFACE_DOWN: - cleanupUpstream(); - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception e) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); - break; - } - mTetherController.notifyInterfaceTetheringReadiness(false, TetherInterfaceStateMachine.this); - if (message.what == CMD_TETHER_UNREQUESTED) { - if (mUsb) { - if (!configureUsbIface(false, mIfaceName)) { - setLastError( - ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - } - } - transitionTo(mInitialState); - } else if (message.what == CMD_INTERFACE_DOWN) { - transitionTo(mUnavailableState); - } - if (DBG) Log.d(TAG, "Untethered " + mIfaceName); + transitionTo(mUnavailableState); + if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName); break; case CMD_TETHER_CONNECTION_CHANGED: String newUpstreamIfaceName = (String)(message.obj); @@ -314,13 +304,6 @@ public class TetherInterfaceStateMachine extends StateMachine { newUpstreamIfaceName); } catch (Exception e) { Log.e(TAG, "Exception enabling Nat: " + e.toString()); - try { - mNMService.disableNat(mIfaceName, newUpstreamIfaceName); - } catch (Exception ee) {} - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception ee) {} - setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR); transitionTo(mInitialState); return true; @@ -333,14 +316,6 @@ public class TetherInterfaceStateMachine extends StateMachine { case CMD_START_TETHERING_ERROR: case CMD_STOP_TETHERING_ERROR: case CMD_SET_DNS_FORWARDERS_ERROR: - cleanupUpstream(); - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception e) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); - break; - } setLastErrorAndTransitionToInitialState( ConnectivityManager.TETHER_ERROR_MASTER_ERROR); break; diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java index 2c54f3b869f0..441084696b26 100644 --- a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java +++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java @@ -18,6 +18,8 @@ package com.android.server.connectivity.tethering; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -54,7 +56,7 @@ public class TetherInterfaceStateMachineTest { private final TestLooper mLooper = new TestLooper(); private TetherInterfaceStateMachine mTestedSm; - private void initStateMachine(boolean isUsb) { + private void initStateMachine(boolean isUsb) throws Exception { mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), isUsb, mNMService, mStatsService, mTetherHelper); mTestedSm.start(); @@ -62,15 +64,17 @@ public class TetherInterfaceStateMachineTest { // the test of the world that we've changed from an unknown to available state. mLooper.dispatchAll(); reset(mNMService, mStatsService, mTetherHelper); + when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); } - private void initTetheredStateMachine(boolean isUsb, String upstreamIface) { + private void initTetheredStateMachine(boolean isUsb, String upstreamIface) throws Exception { initStateMachine(isUsb); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); if (upstreamIface != null) { dispatchTetherConnectionChanged(upstreamIface); } reset(mNMService, mStatsService, mTetherHelper); + when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); } @Before @@ -92,7 +96,7 @@ public class TetherInterfaceStateMachineTest { } @Test - public void shouldDoNothingUntilRequested() { + public void shouldDoNothingUntilRequested() throws Exception { initStateMachine(false); final int [] NOOP_COMMANDS = { TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED, @@ -112,7 +116,7 @@ public class TetherInterfaceStateMachineTest { } @Test - public void handlesImmediateInterfaceDown() { + public void handlesImmediateInterfaceDown() throws Exception { initStateMachine(false); dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); verify(mTetherHelper).sendTetherStateChangedBroadcast(); @@ -124,7 +128,7 @@ public class TetherInterfaceStateMachineTest { } @Test - public void canBeTethered() throws RemoteException { + public void canBeTethered() throws Exception { initStateMachine(false); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); InOrder inOrder = inOrder(mTetherHelper, mNMService); @@ -144,8 +148,8 @@ public class TetherInterfaceStateMachineTest { dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); - inOrder.verify(mNMService).untetherInterface(IFACE_NAME); inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + inOrder.verify(mNMService).untetherInterface(IFACE_NAME); inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); assertTrue("Should be ready for tethering again", mTestedSm.isAvailable()); @@ -154,12 +158,10 @@ public class TetherInterfaceStateMachineTest { } @Test - public void canBeTetheredAsUsb() throws RemoteException { + public void canBeTetheredAsUsb() throws Exception { initStateMachine(true); - when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); - InOrder inOrder = inOrder(mTetherHelper, mNMService); inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); @@ -211,11 +213,11 @@ public class TetherInterfaceStateMachineTest { dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); inOrder.verify(mStatsService).forceUpdate(); inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNMService).untetherInterface(IFACE_NAME); - inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); assertTrue("Should be ready for tethering again", mTestedSm.isAvailable()); @@ -223,6 +225,53 @@ public class TetherInterfaceStateMachineTest { assertFalse("Should have no errors", mTestedSm.isErrored()); } + @Test + public void interfaceDownLeadsToUnavailable() throws Exception { + for (boolean shouldThrow : new boolean[]{true, false}) { + initTetheredStateMachine(true, null); + + if (shouldThrow) { + doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME); + } + dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); + InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration); + usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); + usbTeardownOrder.verify(mNMService).setInterfaceConfig( + IFACE_NAME, mInterfaceConfiguration); + verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + assertFalse("Should not be available", mTestedSm.isAvailable()); + assertFalse("Should not be tethered", mTestedSm.isTethered()); + } + } + + @Test + public void usbShouldBeTornDownOnTetherError() throws Exception { + initStateMachine(true); + + doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME); + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration); + usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); + usbTeardownOrder.verify(mNMService).setInterfaceConfig( + IFACE_NAME, mInterfaceConfiguration); + // Initial call is when we transition to the tethered state on request. + verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); + // And this call is to notify that we really aren't requested tethering. + verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + assertTrue("Expected to see an error reported", mTestedSm.isErrored()); + } + + @Test + public void shouldTearDownUsbOnUpstreamError() throws Exception { + initTetheredStateMachine(true, null); + + doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString()); + dispatchTetherConnectionChanged(UPSTREAM_IFACE); + InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration); + usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); + usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); + verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + } /** * Send a command to the state machine under test, and run the event loop to idle. -- cgit v1.2.3-59-g8ed1b From e949039081b08626590be9783c85f358955e1b82 Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Thu, 26 May 2016 15:57:29 -0700 Subject: Use an ArrayMap instead of HashMap to track tether interfaces ( cherry-pick of 36f6f4e394750e4989d78f38a31ab8f2f66c07f7 ) Since the number of tetherable interfaces should be small (e.g. <10), the computational cost of a looking up objects in an ArrayMap is negligible, and the memory required is less than a HashMap. While here, remove some unused imports. Change-Id: I3fb7091cfb93697d2fc6388b843d60f7e8e7e1a6 Test: Compiles. --- .../core/java/com/android/server/connectivity/Tethering.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 05823b5f4295..40d17edc5a66 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -34,8 +34,6 @@ import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.INetworkStatsService; -import android.net.InterfaceConfiguration; -import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; @@ -58,12 +56,12 @@ import android.provider.Settings; import android.telephony.CarrierConfigManager; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.util.IState; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.MessageUtils; import com.android.internal.util.Protocol; @@ -83,6 +81,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; +import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -127,7 +126,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering private final INetworkStatsService mStatsService; private final Looper mLooper; - private HashMap mIfaces; // all tethered/tetherable ifaces + private Map mIfaces; // all tethered/tetherable ifaces private BroadcastReceiver mStateReceiver; @@ -172,7 +171,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering mPublicSync = new Object(); - mIfaces = new HashMap(); + mIfaces = new ArrayMap(); // make our own thread so we don't anr the system mLooper = IoThread.get().getLooper(); -- cgit v1.2.3-59-g8ed1b From 5c0b10a4a9eecc5307bb89a271221f2b20448797 Mon Sep 17 00:00:00 2001 From: Christopher Wiley Date: Tue, 31 May 2016 14:43:08 -0700 Subject: Tethering: Own WiFi tethering state and lifetime ( cherry-pick of f1315c3cd6d77e812ae32fe038b4e8bf2e70d5bf ) - Add logic to Tethering to track whether the user has requested tethering via WiFi. - Subscribe to intents regarding soft AP state to enable and disable tethering when the AP comes up or goes down. - Refactor IP configuration logic to do configuration for WiFi as well as USB. Bug: 29054780 Test: WiFi tethering continues to work on angler Tethering related unittests continue to pass. Change-Id: I6eff2573ca3fd11fabcf138c468ba517ff2daf65 --- .../com/android/server/connectivity/Tethering.java | 168 +++++++++++++-------- .../tethering/TetherInterfaceStateMachine.java | 49 +++--- .../tethering/TetherInterfaceStateMachineTest.java | 35 ++--- 3 files changed, 153 insertions(+), 99 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 40d17edc5a66..4a0e81b42734 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -128,7 +128,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering private Map mIfaces; // all tethered/tetherable ifaces - private BroadcastReceiver mStateReceiver; + private final BroadcastReceiver mStateReceiver; // {@link ComponentName} of the Service used to run tether provisioning. private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources @@ -163,6 +163,9 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering private boolean mUsbTetherRequested; // true if USB tethering should be started // when RNDIS is enabled + // True iff WiFi tethering should be started when soft AP is ready. + private boolean mWifiTetherRequested; + public Tethering(Context context, INetworkManagementService nmService, INetworkStatsService statsService) { mContext = context; @@ -184,6 +187,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_STATE); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); mContext.registerReceiver(mStateReceiver, filter); @@ -245,29 +249,22 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering // Never called directly: only called from interfaceLinkStateChanged. // See NetlinkHandler.cpp:71. if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up); - boolean found = false; - boolean usb = false; synchronized (mPublicSync) { - if (isWifi(iface)) { - found = true; - } else if (isUsb(iface)) { - found = true; - usb = true; - } else if (isBluetooth(iface)) { - found = true; + int interfaceType = ifaceNameToType(iface); + if (interfaceType == ConnectivityManager.TETHERING_INVALID) { + return; } - if (found == false) return; TetherInterfaceStateMachine sm = mIfaces.get(iface); if (up) { if (sm == null) { - sm = new TetherInterfaceStateMachine(iface, mLooper, usb, + sm = new TetherInterfaceStateMachine(iface, mLooper, interfaceType, mNMService, mStatsService, this); mIfaces.put(iface, sm); sm.start(); } } else { - if (isUsb(iface)) { + if (interfaceType == ConnectivityManager.TETHERING_USB) { // ignore usb0 down after enabling RNDIS // we will handle disconnect in interfaceRemoved instead if (VDBG) Log.d(TAG, "ignore interface down for " + iface); @@ -293,7 +290,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } } - public boolean isWifi(String iface) { + private boolean isWifi(String iface) { synchronized (mPublicSync) { for (String regex : mTetherableWifiRegexs) { if (iface.matches(regex)) return true; @@ -302,7 +299,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } } - public boolean isBluetooth(String iface) { + private boolean isBluetooth(String iface) { synchronized (mPublicSync) { for (String regex : mTetherableBluetoothRegexs) { if (iface.matches(regex)) return true; @@ -311,23 +308,23 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } } + private int ifaceNameToType(String iface) { + if (isWifi(iface)) { + return ConnectivityManager.TETHERING_WIFI; + } else if (isUsb(iface)) { + return ConnectivityManager.TETHERING_USB; + } else if (isBluetooth(iface)) { + return ConnectivityManager.TETHERING_BLUETOOTH; + } + return ConnectivityManager.TETHERING_INVALID; + } + @Override public void interfaceAdded(String iface) { if (VDBG) Log.d(TAG, "interfaceAdded " + iface); - boolean found = false; - boolean usb = false; synchronized (mPublicSync) { - if (isWifi(iface)) { - found = true; - } - if (isUsb(iface)) { - found = true; - usb = true; - } - if (isBluetooth(iface)) { - found = true; - } - if (found == false) { + int interfaceType = ifaceNameToType(iface); + if (interfaceType == ConnectivityManager.TETHERING_INVALID) { if (VDBG) Log.d(TAG, iface + " is not a tetherable iface, ignoring"); return; } @@ -337,7 +334,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring"); return; } - sm = new TetherInterfaceStateMachine(iface, mLooper, usb, + sm = new TetherInterfaceStateMachine(iface, mLooper, interfaceType, mNMService, mStatsService, this); mIfaces.put(iface, sm); sm.start(); @@ -412,24 +409,19 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering * for the specified interface. */ private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) { - boolean isProvisioningRequired = isTetherProvisioningRequired(); + boolean isProvisioningRequired = enable && isTetherProvisioningRequired(); + int result; switch (type) { case ConnectivityManager.TETHERING_WIFI: - final WifiManager wifiManager = - (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); - if (wifiManager.setWifiApEnabled(null, enable)) { - sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_NO_ERROR); - if (enable && isProvisioningRequired) { - scheduleProvisioningRechecks(type); - } - } else{ - sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_MASTER_ERROR); + result = setWifiTethering(enable); + if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) { + scheduleProvisioningRechecks(type); } + sendTetherResult(receiver, result); break; case ConnectivityManager.TETHERING_USB: - int result = setUsbTethering(enable); - if (enable && isProvisioningRequired && - result == ConnectivityManager.TETHER_ERROR_NO_ERROR) { + result = setUsbTethering(enable); + if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) { scheduleProvisioningRechecks(type); } sendTetherResult(receiver, result); @@ -449,6 +441,20 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } } + private int setWifiTethering(final boolean enable) { + synchronized (mPublicSync) { + // Note that we're maintaining a predicate that mWifiTetherRequested always matches + // our last request to WifiManager re: its AP enabled status. + mWifiTetherRequested = enable; + final WifiManager wifiManager = + (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + if (wifiManager.setWifiApEnabled(null /* use existing wifi config */, enable)) { + return ConnectivityManager.TETHER_ERROR_NO_ERROR; + } + return ConnectivityManager.TETHER_ERROR_MASTER_ERROR; + } + } + private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) { final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter == null || !adapter.isEnabled()) { @@ -770,7 +776,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering mRndisEnabled = intent.getBooleanExtra(UsbManager.USB_FUNCTION_RNDIS, false); // start tethering if we have a request pending if (usbConnected && mRndisEnabled && mUsbTetherRequested) { - tetherUsb(true); + tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB); } mUsbTetherRequested = false; } @@ -782,31 +788,72 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION"); mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED); } + } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) { + synchronized (Tethering.this.mPublicSync) { + if (!mWifiTetherRequested) { + // We only care when we're trying to tether via our WiFi interface. + return; + } + int curState = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE, + WifiManager.WIFI_AP_STATE_DISABLED); + switch (curState) { + case WifiManager.WIFI_AP_STATE_ENABLING: + // We can see this state on the way to both enabled and failure states. + break; + case WifiManager.WIFI_AP_STATE_ENABLED: + // Tell an appropriate interface state machine that it should tether. + tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI); + break; + case WifiManager.WIFI_AP_STATE_DISABLED: + case WifiManager.WIFI_AP_STATE_DISABLING: + case WifiManager.WIFI_AP_STATE_FAILED: + default: + if (DBG) { + Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" + + curState); + } + // Tell an appropriate interface state machine that + // it needs to tear itself down. + tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_WIFI); + setWifiTethering(false); + break; + } + } } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { updateConfiguration(); } } } - private void tetherUsb(boolean enable) { - if (VDBG) Log.d(TAG, "tetherUsb " + enable); + private void tetherMatchingInterfaces(boolean enable, int interfaceType) { + if (VDBG) Log.d(TAG, "tetherMatchingInterfaces(" + enable + ", " + interfaceType + ")"); - String[] ifaces = new String[0]; + String[] ifaces = null; try { ifaces = mNMService.listInterfaces(); } catch (Exception e) { Log.e(TAG, "Error listing Interfaces", e); return; } - for (String iface : ifaces) { - if (isUsb(iface)) { - int result = (enable ? tether(iface) : untether(iface)); - if (result == ConnectivityManager.TETHER_ERROR_NO_ERROR) { - return; + String chosenIface = null; + if (ifaces != null) { + for (String iface : ifaces) { + if (ifaceNameToType(iface) == interfaceType) { + chosenIface = iface; + break; } } } - Log.e(TAG, "unable start or stop USB tethering"); + if (chosenIface == null) { + Log.e(TAG, "could not find iface of type " + interfaceType); + return; + } + + int result = (enable ? tether(chosenIface) : untether(chosenIface)); + if (result != ConnectivityManager.TETHER_ERROR_NO_ERROR) { + Log.e(TAG, "unable start or stop tethering on iface " + chosenIface); + return; + } } // TODO - return copies so people can't tamper @@ -831,7 +878,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering if (mRndisEnabled) { final long ident = Binder.clearCallingIdentity(); try { - tetherUsb(true); + tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB); } finally { Binder.restoreCallingIdentity(ident); } @@ -842,7 +889,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } else { final long ident = Binder.clearCallingIdentity(); try { - tetherUsb(false); + tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_USB); } finally { Binder.restoreCallingIdentity(ident); } @@ -1410,15 +1457,10 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering for (String iface : ifaces) { TetherInterfaceStateMachine sm = mIfaces.get(iface); if (sm != null && sm.isTethered()) { - if (isUsb(iface)) { - tethered.add(new Integer( - ConnectivityManager.TETHERING_USB)); - } else if (isWifi(iface)) { - tethered.add(new Integer( - ConnectivityManager.TETHERING_WIFI)); - } else if (isBluetooth(iface)) { - tethered.add(new Integer( - ConnectivityManager.TETHERING_BLUETOOTH)); + int interfaceType = ifaceNameToType(iface); + if (interfaceType != + ConnectivityManager.TETHERING_INVALID) { + tethered.add(new Integer(interfaceType)); } } } diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java index e33406e67061..b8bea60a5ccb 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java @@ -43,6 +43,8 @@ import java.net.InetAddress; public class TetherInterfaceStateMachine extends StateMachine { private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129"; private static final int USB_PREFIX_LENGTH = 24; + private static final String WIFI_HOST_IFACE_ADDR = "192.168.43.1"; + private static final int WIFI_HOST_IFACE_PREFIX_LENGTH = 24; private final static String TAG = "TetherInterfaceSM"; private final static boolean DBG = false; @@ -81,13 +83,13 @@ public class TetherInterfaceStateMachine extends StateMachine { private final INetworkStatsService mStatsService; private final IControlsTethering mTetherController; - private final boolean mUsb; private final String mIfaceName; + private final int mInterfaceType; private int mLastError; private String mMyUpstreamIfaceName; // may change over time - public TetherInterfaceStateMachine(String ifaceName, Looper looper, boolean usb, + public TetherInterfaceStateMachine(String ifaceName, Looper looper, int interfaceType, INetworkManagementService nMService, INetworkStatsService statsService, IControlsTethering tetherController) { super(ifaceName, looper); @@ -95,7 +97,7 @@ public class TetherInterfaceStateMachine extends StateMachine { mStatsService = statsService; mTetherController = tetherController; mIfaceName = ifaceName; - mUsb = usb; + mInterfaceType = interfaceType; setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); mInitialState = new InitialState(); @@ -143,25 +145,38 @@ public class TetherInterfaceStateMachine extends StateMachine { } // configured when we start tethering and unconfig'd on error or conclusion - private boolean configureUsbIface(boolean enabled, String iface) { - if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); + private boolean configureIfaceIp(boolean enabled) { + if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")"); + + String ipAsString = null; + int prefixLen = 0; + if (mInterfaceType == ConnectivityManager.TETHERING_USB) { + ipAsString = USB_NEAR_IFACE_ADDR; + prefixLen = USB_PREFIX_LENGTH; + } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) { + ipAsString = WIFI_HOST_IFACE_ADDR; + prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH; + } else { + // Nothing to do, BT does this elsewhere. + return true; + } InterfaceConfiguration ifcg = null; try { - ifcg = mNMService.getInterfaceConfig(iface); + ifcg = mNMService.getInterfaceConfig(mIfaceName); if (ifcg != null) { - InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); - ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH)); + InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString); + ifcg.setLinkAddress(new LinkAddress(addr, prefixLen)); if (enabled) { ifcg.setInterfaceUp(); } else { ifcg.setInterfaceDown(); } ifcg.clearFlag("running"); - mNMService.setInterfaceConfig(iface, ifcg); + mNMService.setInterfaceConfig(mIfaceName, ifcg); } } catch (Exception e) { - Log.e(TAG, "Error configuring interface " + iface, e); + Log.e(TAG, "Error configuring interface " + mIfaceName, e); return false; } @@ -205,12 +220,10 @@ public class TetherInterfaceStateMachine extends StateMachine { class TetheredState extends State { @Override public void enter() { - if (mUsb) { - if (!configureUsbIface(true, mIfaceName)) { - setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - transitionTo(mInitialState); - return; - } + if (!configureIfaceIp(true)) { + setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); + transitionTo(mInitialState); + return; } try { @@ -242,9 +255,7 @@ public class TetherInterfaceStateMachine extends StateMachine { Log.e(TAG, "Failed to untether interface: " + ee.toString()); } - if (mUsb) { - configureUsbIface(false, mIfaceName); - } + configureIfaceIp(false); } private void cleanupUpstream() { diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java index 441084696b26..30a7dbc80977 100644 --- a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java +++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.net.ConnectivityManager; import android.net.INetworkStatsService; import android.net.InterfaceConfiguration; import android.os.INetworkManagementService; @@ -56,8 +57,8 @@ public class TetherInterfaceStateMachineTest { private final TestLooper mLooper = new TestLooper(); private TetherInterfaceStateMachine mTestedSm; - private void initStateMachine(boolean isUsb) throws Exception { - mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), isUsb, + private void initStateMachine(int interfaceType) throws Exception { + mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), interfaceType, mNMService, mStatsService, mTetherHelper); mTestedSm.start(); // Starting the state machine always puts us in a consistent state and notifies @@ -67,8 +68,8 @@ public class TetherInterfaceStateMachineTest { when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); } - private void initTetheredStateMachine(boolean isUsb, String upstreamIface) throws Exception { - initStateMachine(isUsb); + private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception { + initStateMachine(interfaceType); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); if (upstreamIface != null) { dispatchTetherConnectionChanged(upstreamIface); @@ -84,8 +85,8 @@ public class TetherInterfaceStateMachineTest { @Test public void startsOutAvailable() { - mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), false, - mNMService, mStatsService, mTetherHelper); + mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), + ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper); mTestedSm.start(); mLooper.dispatchAll(); assertTrue("Should start out available for tethering", mTestedSm.isAvailable()); @@ -97,7 +98,7 @@ public class TetherInterfaceStateMachineTest { @Test public void shouldDoNothingUntilRequested() throws Exception { - initStateMachine(false); + initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH); final int [] NOOP_COMMANDS = { TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED, TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR, @@ -117,7 +118,7 @@ public class TetherInterfaceStateMachineTest { @Test public void handlesImmediateInterfaceDown() throws Exception { - initStateMachine(false); + initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH); dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); verify(mTetherHelper).sendTetherStateChangedBroadcast(); verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); @@ -129,7 +130,7 @@ public class TetherInterfaceStateMachineTest { @Test public void canBeTethered() throws Exception { - initStateMachine(false); + initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); InOrder inOrder = inOrder(mTetherHelper, mNMService); inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); @@ -144,7 +145,7 @@ public class TetherInterfaceStateMachineTest { @Test public void canUnrequestTethering() throws Exception { - initTetheredStateMachine(false, null); + initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); @@ -159,7 +160,7 @@ public class TetherInterfaceStateMachineTest { @Test public void canBeTetheredAsUsb() throws Exception { - initStateMachine(true); + initStateMachine(ConnectivityManager.TETHERING_USB); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); InOrder inOrder = inOrder(mTetherHelper, mNMService); @@ -177,7 +178,7 @@ public class TetherInterfaceStateMachineTest { @Test public void handlesFirstUpstreamChange() throws Exception { - initTetheredStateMachine(false, null); + initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null); // Telling the state machine about its upstream interface triggers a little more configuration. dispatchTetherConnectionChanged(UPSTREAM_IFACE); @@ -192,7 +193,7 @@ public class TetherInterfaceStateMachineTest { @Test public void handlesChangingUpstream() throws Exception { - initTetheredStateMachine(false, UPSTREAM_IFACE); + initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE); dispatchTetherConnectionChanged(UPSTREAM_IFACE2); InOrder inOrder = inOrder(mNMService, mStatsService); @@ -209,7 +210,7 @@ public class TetherInterfaceStateMachineTest { @Test public void canUnrequestTetheringWithUpstream() throws Exception { - initTetheredStateMachine(false, UPSTREAM_IFACE); + initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); @@ -228,7 +229,7 @@ public class TetherInterfaceStateMachineTest { @Test public void interfaceDownLeadsToUnavailable() throws Exception { for (boolean shouldThrow : new boolean[]{true, false}) { - initTetheredStateMachine(true, null); + initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null); if (shouldThrow) { doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME); @@ -246,7 +247,7 @@ public class TetherInterfaceStateMachineTest { @Test public void usbShouldBeTornDownOnTetherError() throws Exception { - initStateMachine(true); + initStateMachine(ConnectivityManager.TETHERING_USB); doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); @@ -263,7 +264,7 @@ public class TetherInterfaceStateMachineTest { @Test public void shouldTearDownUsbOnUpstreamError() throws Exception { - initTetheredStateMachine(true, null); + initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null); doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString()); dispatchTetherConnectionChanged(UPSTREAM_IFACE); -- cgit v1.2.3-59-g8ed1b