Use TetherEnabler in AllInOneTetherSettings to manage master switch

In AllInOneTetherSettings, we use TetherEnabler to hanle all behavior
related to tethering switch on/off.
In TetherEnbler, add WifiManager.WIFI_AP_STATE_CHANGED_ACTION to cover
all possible tethering state change.
TetherEnablerTest is modified accordingly.

Bug: 147322704
Test: make RunSettingsRoboTests ROBOTEST_FILTER=CodeInspectionTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=TetherEnablerTest
Test: make RunSettingsRoboTests
ROBOTEST_FILTER=AllInOneTetherSettingsTest

Change-Id: I505b3825f79260983fff9d3935ba834ad8f9f690
diff --git a/src/com/android/settings/AllInOneTetherSettings.java b/src/com/android/settings/AllInOneTetherSettings.java
index d6366b2..fbf0280 100644
--- a/src/com/android/settings/AllInOneTetherSettings.java
+++ b/src/com/android/settings/AllInOneTetherSettings.java
@@ -20,21 +20,20 @@
 import static android.net.ConnectivityManager.TETHERING_WIFI;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_CHANGED_ACTION;
 
-import static com.android.settings.network.WifiTetherDisablePreferenceController
-        .KEY_ENABLE_WIFI_TETHERING;
+import static com.android.settings.network.WifiTetherDisablePreferenceController.KEY_ENABLE_WIFI_TETHERING;
 
 import android.app.settings.SettingsEnums;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
-import android.net.ConnectivityManager;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.UserManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -47,6 +46,8 @@
 import com.android.settings.datausage.DataSaverBackend;
 import com.android.settings.network.TetherEnabler;
 import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.widget.SwitchBar;
+import com.android.settings.widget.SwitchBarController;
 import com.android.settings.wifi.tether.WifiTetherApBandPreferenceController;
 import com.android.settings.wifi.tether.WifiTetherAutoOffPreferenceController;
 import com.android.settings.wifi.tether.WifiTetherBasePreferenceController;
@@ -59,10 +60,10 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
- * Displays preferences for Tethering.
- * TODO(b/147322704): Use TetherEnabler in this fragment to manage tethering switch on/off.
+ * Displays preferences for all Tethering options.
  * TODO(b/147323306): Add tether option preferences into this fragment after controllers created.
  */
 @SearchIndexable
@@ -96,6 +97,7 @@
 
     private WifiManager mWifiManager;
     private boolean mRestartWifiApAfterConfigChange;
+    private final AtomicReference<BluetoothPan> mBluetoothPan = new AtomicReference<>();
 
     private WifiTetherSSIDPreferenceController mSSIDPreferenceController;
     private WifiTetherPasswordPreferenceController mPasswordPreferenceController;
@@ -103,8 +105,8 @@
     private WifiTetherSecurityPreferenceController mSecurityPreferenceController;
     private PreferenceGroup mWifiTetherGroup;
     private SharedPreferences mSharedPreferences;
-    private ConnectivityManager mConnectivityManager;
     private boolean mWifiTetherChosen;
+    private TetherEnabler mTetherEnabler;
 
     private final BroadcastReceiver mTetherChangeReceiver = new BroadcastReceiver() {
         @Override
@@ -118,19 +120,30 @@
                 if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_DISABLED
                         && mRestartWifiApAfterConfigChange) {
                     mRestartWifiApAfterConfigChange = false;
-                    startTether();
+                    mTetherEnabler.startTethering(TETHERING_WIFI);
                 }
             } else if (TextUtils.equals(action, WIFI_AP_STATE_CHANGED_ACTION)) {
                 int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE, 0);
                 if (state == WifiManager.WIFI_AP_STATE_DISABLED
                         && mRestartWifiApAfterConfigChange) {
                     mRestartWifiApAfterConfigChange = false;
-                    startTether();
+                    mTetherEnabler.startTethering(TETHERING_WIFI);
                 }
             }
         }
     };
 
+    private final BluetoothProfile.ServiceListener mProfileServiceListener =
+            new BluetoothProfile.ServiceListener() {
+                public void onServiceConnected(int profile, BluetoothProfile proxy) {
+                    mBluetoothPan.set((BluetoothPan) proxy);
+                }
+
+                public void onServiceDisconnected(int profile) {
+                    mBluetoothPan.set(null);
+                }
+            };
+
     @Override
     public int getMetricsCategory() {
         return SettingsEnums.TETHER;
@@ -144,8 +157,6 @@
     public void onAttach(Context context) {
         super.onAttach(context);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-        mConnectivityManager =
-                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
         mSharedPreferences =
                 context.getSharedPreferences(TetherEnabler.SHARED_PREF, Context.MODE_PRIVATE);
 
@@ -182,6 +193,27 @@
     }
 
     @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        if (mUnavailable) {
+            return;
+        }
+        // Assume we are in a SettingsActivity. This is only safe because we currently use
+        // SettingsActivity as base for all preference fragments.
+        final SettingsActivity activity = (SettingsActivity) getActivity();
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            adapter.getProfileProxy(activity.getApplicationContext(), mProfileServiceListener,
+                    BluetoothProfile.PAN);
+        }
+        final SwitchBar switchBar = activity.getSwitchBar();
+        mTetherEnabler = new TetherEnabler(activity,
+                new SwitchBarController(switchBar), mBluetoothPan);
+        getSettingsLifecycle().addObserver(mTetherEnabler);
+        switchBar.show();
+    }
+
+    @Override
     public void onStart() {
         super.onStart();
 
@@ -297,8 +329,7 @@
                 Log.d(TAG, "Wifi AP config changed while enabled, stop and restart");
             }
             mRestartWifiApAfterConfigChange = true;
-            // TODO(b/147322704): Use TethetEnabler to stop tethering.
-            mConnectivityManager.stopTethering(TETHERING_WIFI);
+            mTetherEnabler.stopTethering(TETHERING_WIFI);
         }
 
         if (controller instanceof WifiTetherSecurityPreferenceController) {
@@ -335,23 +366,6 @@
         }
     }
 
-    private void startTether() {
-        // TODO(b/147322704): Use TetherEnabler to start tethering.
-        if (mWifiManager.isWifiApEnabled()) {
-            return;
-        }
-        mConnectivityManager.startTethering(ConnectivityManager.TETHERING_WIFI,
-                true /*showProvisioningUi*/,
-                new ConnectivityManager.OnStartTetheringCallback() {
-                    @Override
-                    public void onTetheringFailed() {
-                        super.onTetheringFailed();
-                        // Do nothing. There is no UI to update at this point.
-                    }
-                },
-                new Handler(Looper.getMainLooper()));
-    }
-
     private void reConfigInitialExpandedChildCount() {
         getPreferenceScreen().setInitialExpandedChildrenCount(getInitialExpandedChildCount());
     }
diff --git a/src/com/android/settings/network/BluetoothTetherPreferenceController.java b/src/com/android/settings/network/BluetoothTetherPreferenceController.java
index 10849a0..327b6ac 100644
--- a/src/com/android/settings/network/BluetoothTetherPreferenceController.java
+++ b/src/com/android/settings/network/BluetoothTetherPreferenceController.java
@@ -47,8 +47,7 @@
 
     private static final String TAG = "BluetoothTetherPreferenceController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    @VisibleForTesting
-    static final String PREF_KEY = "enable_bluetooth_tethering";
+    public static final String PREF_KEY = "enable_bluetooth_tethering";
     private final ConnectivityManager mCm;
     private int mBluetoothState;
     private Preference mPreference;
diff --git a/src/com/android/settings/network/TetherEnabler.java b/src/com/android/settings/network/TetherEnabler.java
index 9021a26..e2b4c68 100644
--- a/src/com/android/settings/network/TetherEnabler.java
+++ b/src/com/android/settings/network/TetherEnabler.java
@@ -42,6 +42,7 @@
 import com.android.settings.datausage.DataSaverBackend;
 import com.android.settings.widget.SwitchWidgetController;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -78,14 +79,7 @@
     private final Context mContext;
 
     @VisibleForTesting
-    final ConnectivityManager.OnStartTetheringCallback mOnStartTetheringCallback =
-            new ConnectivityManager.OnStartTetheringCallback() {
-                @Override
-                public void onTetheringFailed() {
-                    super.onTetheringFailed();
-                    mSwitchWidgetController.setChecked(false);
-                }
-            };
+    ConnectivityManager.OnStartTetheringCallback mOnStartTetheringCallback;
     private final AtomicReference<BluetoothPan> mBluetoothPan;
     private final SharedPreferences mSharedPreferences;
     private boolean mBluetoothEnableForTether;
@@ -110,11 +104,15 @@
         mDataSaverBackend.addListener(this);
         mSwitchWidgetController.setListener(this);
         mSwitchWidgetController.startListening();
-        IntentFilter filter = new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
+
+        final IntentFilter filter = new IntentFilter(
+                ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
+        filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
         mContext.registerReceiver(mTetherChangeReceiver, filter);
-        mSwitchWidgetController.setChecked(isTethering());
-        setSwitchWidgetEnabled(true);
+
+        mOnStartTetheringCallback = new OnStartTetheringCallback(this);
+        updateState();
     }
 
     @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
@@ -134,8 +132,14 @@
         mContext.unregisterReceiver(mTetherChangeReceiver);
     }
 
-    private void setSwitchWidgetEnabled(boolean enabled) {
-        mSwitchWidgetController.setEnabled(enabled && !mDataSaverEnabled);
+    private void updateState() {
+        mSwitchWidgetController.setChecked(isTethering());
+        mSwitchWidgetController.setEnabled(!mDataSaverEnabled);
+    }
+
+    private void updateState(String[] tethered) {
+        mSwitchWidgetController.setChecked(isTethering(tethered));
+        mSwitchWidgetController.setEnabled(!mDataSaverEnabled);
     }
 
     private boolean isTethering() {
@@ -148,6 +152,10 @@
             return true;
         }
 
+        if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) {
+            return true;
+        }
+
         final BluetoothPan pan = mBluetoothPan.get();
 
         return pan != null && pan.isTetheringOn();
@@ -155,31 +163,41 @@
 
     @Override
     public boolean onSwitchToggled(boolean isChecked) {
-        if (isChecked) {
+        if (isChecked && !isTethering()) {
             startTether();
-        } else {
+        }
+
+        if (!isChecked && isTethering()) {
             stopTether();
         }
         return true;
     }
 
-    @VisibleForTesting
-    void stopTether() {
+    private void stopTether() {
 
         // Wi-Fi tether is selected by default.
         if (mSharedPreferences.getBoolean(WIFI_TETHER_KEY, true)) {
-            mConnectivityManager.stopTethering(TETHERING_WIFI);
+            stopTethering(TETHERING_WIFI);
         }
 
         if (mSharedPreferences.getBoolean(USB_TETHER_KEY, false)) {
-            mConnectivityManager.stopTethering(TETHERING_USB);
+            stopTethering(TETHERING_USB);
         }
 
         if (mSharedPreferences.getBoolean(BLUETOOTH_TETHER_KEY, false)) {
-            mConnectivityManager.stopTethering(TETHERING_BLUETOOTH);
+            stopTethering(TETHERING_BLUETOOTH);
         }
     }
 
+    /**
+     * Use this method to stop a single choice of tethering.
+     *
+     * @param choice The choice of tethering to stop.
+     */
+    public void stopTethering(int choice) {
+        mConnectivityManager.stopTethering(choice);
+    }
+
     @VisibleForTesting
     void startTether() {
 
@@ -197,8 +215,16 @@
         }
     }
 
-    @VisibleForTesting
-    void startTethering(int choice) {
+    /**
+     * Use this method to start a single choice of tethering.
+     * For bluetooth tethering, it will first turn on bluetooth if bluetooth is off.
+     * For Wi-Fi tethering, it will be no-op if Wi-Fi tethering already active.
+     *
+     * @param choice The choice of tethering to start.
+     */
+    public void startTethering(int choice) {
+        mSwitchWidgetController.setEnabled(false);
+
         if (choice == TETHERING_WIFI && mWifiManager.isWifiApEnabled()) {
             if (DEBUG) {
                 Log.d(TAG, "Wifi tether already active!");
@@ -224,36 +250,67 @@
     private final BroadcastReceiver mTetherChangeReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
+            final String action = intent.getAction();
+            ArrayList<String> active = null;
+            boolean shouldUpdateState = false;
             if (TextUtils.equals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED, action)) {
-                ArrayList<String> active = intent.getStringArrayListExtra(
-                        ConnectivityManager.EXTRA_ACTIVE_TETHER);
-                mSwitchWidgetController.setChecked(
-                        isTethering(active.toArray(new String[active.size()])));
+                active = intent.getStringArrayListExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER);
+                shouldUpdateState = true;
+            } else if (TextUtils.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION, action)) {
+                shouldUpdateState = handleWifiApStateChanged(intent.getIntExtra(
+                        WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED));
             } else if (TextUtils.equals(BluetoothAdapter.ACTION_STATE_CHANGED, action)) {
-                switch (intent
-                        .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
-                    case BluetoothAdapter.STATE_ON:
-                        if (mBluetoothEnableForTether) {
-                            startTethering(TETHERING_BLUETOOTH);
-                        }
-                        // Fall through.
-                    case BluetoothAdapter.STATE_OFF:
-                        // Fall through.
-                    case BluetoothAdapter.ERROR:
-                        mBluetoothEnableForTether = false;
-                        break;
-                    default:
-                        // ignore transition states
+                shouldUpdateState = handleBluetoothStateChanged(intent
+                        .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR));
+            }
+
+            if (shouldUpdateState) {
+                if (active != null) {
+                    updateState(active.toArray(new String[0]));
+                } else {
+                    updateState();
                 }
             }
         }
     };
 
+    private boolean handleBluetoothStateChanged(int state) {
+        switch (state) {
+            case BluetoothAdapter.STATE_ON:
+                if (mBluetoothEnableForTether) {
+                    startTethering(TETHERING_BLUETOOTH);
+                }
+                // Fall through.
+            case BluetoothAdapter.STATE_OFF:
+                // Fall through.
+            case BluetoothAdapter.ERROR:
+                mBluetoothEnableForTether = false;
+                return true;
+            default:
+                // Return false for transition states.
+                return false;
+        }
+    }
+
+    private boolean handleWifiApStateChanged(int state) {
+        switch (state) {
+            case WifiManager.WIFI_AP_STATE_FAILED:
+                Log.e(TAG, "Wifi AP is failed!");
+                // fall through
+            case WifiManager.WIFI_AP_STATE_ENABLED:
+                // fall through
+            case WifiManager.WIFI_AP_STATE_DISABLED:
+                return true;
+            default:
+                // return false for transition state
+                return false;
+        }
+    }
+
     @Override
     public void onDataSaverChanged(boolean isDataSaving) {
         mDataSaverEnabled = isDataSaving;
-        setSwitchWidgetEnabled(!isDataSaving);
+        mSwitchWidgetController.setEnabled(!isDataSaving);
     }
 
     @Override
@@ -291,4 +348,30 @@
             }
         }
     }
+
+    private static final class OnStartTetheringCallback extends
+            ConnectivityManager.OnStartTetheringCallback {
+        final WeakReference<TetherEnabler> mTetherEnabler;
+
+        OnStartTetheringCallback(TetherEnabler enabler) {
+            mTetherEnabler = new WeakReference<>(enabler);
+        }
+
+        @Override
+        public void onTetheringStarted() {
+            update();
+        }
+
+        @Override
+        public void onTetheringFailed() {
+            update();
+        }
+
+        private void update() {
+            TetherEnabler enabler = mTetherEnabler.get();
+            if (enabler != null) {
+                enabler.updateState();
+            }
+        }
+    }
 }
diff --git a/src/com/android/settings/network/UsbTetherPreferenceController.java b/src/com/android/settings/network/UsbTetherPreferenceController.java
index 74e8be8..5722480 100644
--- a/src/com/android/settings/network/UsbTetherPreferenceController.java
+++ b/src/com/android/settings/network/UsbTetherPreferenceController.java
@@ -49,8 +49,7 @@
 
     private static final String TAG = "UsbTetherPrefController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    @VisibleForTesting
-    static final String PREF_KEY = "enable_usb_tethering";
+    public static final String PREF_KEY = "enable_usb_tethering";
 
     private final ConnectivityManager mCm;
     private boolean mUsbConnected;
diff --git a/tests/robotests/src/com/android/settings/network/TetherEnablerTest.java b/tests/robotests/src/com/android/settings/network/TetherEnablerTest.java
index 9dcfa89..a2d55dc 100644
--- a/tests/robotests/src/com/android/settings/network/TetherEnablerTest.java
+++ b/tests/robotests/src/com/android/settings/network/TetherEnablerTest.java
@@ -109,8 +109,10 @@
     @Test
     public void startTether_fail_resetSwitchBar() {
         when(mNetworkPolicyManager.getRestrictBackground()).thenReturn(false);
-
+        mEnabler.onStart();
         mEnabler.startTether();
+
+        when(mConnectivityManager.getTetheredIfaces()).thenReturn(new String[0]);
         mEnabler.mOnStartTetheringCallback.onTetheringFailed();
 
         assertThat(mSwitchBar.isChecked()).isFalse();