Refactor hotspot into a full page

- Deprecated WifiAPEnabler, and copied most of its logic into various
  controllers and WifiTetherSettings.
- Added tests

Fix: 37253404
Fix: 36181835

Test: make RunSettingsRoboTests
Change-Id: Iad994d61b694ad7f1113d045a3e7500eeaec178b
diff --git a/res/xml/tether_prefs.xml b/res/xml/tether_prefs.xml
index a506291..709c425 100644
--- a/res/xml/tether_prefs.xml
+++ b/res/xml/tether_prefs.xml
@@ -17,10 +17,16 @@
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                   xmlns:settings="http://schemas.android.com/apk/res-auto">
 
+    <Preference
+        android:key="wifi_tether"
+        android:title="@string/wifi_tether_checkbox_text"
+        android:summary="@string/summary_placeholder"
+        android:fragment="com.android.settings.wifi.tether.WifiTetherSettings" />
+
     <SwitchPreference
         android:key="usb_tether_settings"
         android:title="@string/usb_tethering_button_text"
-        android:summary="@string/usb_tethering_subtext"/>
+        android:summary="@string/usb_tethering_subtext" />
 
     <SwitchPreference
         android:key="enable_wifi_ap"
@@ -35,7 +41,7 @@
     <SwitchPreference
         android:key="enable_bluetooth_tethering"
         android:title="@string/bluetooth_tether_checkbox_text"
-        android:summary="@string/bluetooth_tethering_subtext"/>
+        android:summary="@string/bluetooth_tethering_subtext" />
 
     <Preference
         android:key="disabled_on_data_saver"
diff --git a/res/xml/wifi_tether_settings.xml b/res/xml/wifi_tether_settings.xml
new file mode 100644
index 0000000..b83f2f3
--- /dev/null
+++ b/res/xml/wifi_tether_settings.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2017 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <com.android.settings.widget.ValidatedEditTextPreference
+        android:key="wifi_tether_network_name"
+        android:title="@string/wifi_ssid"
+        android:summary="@string/summary_placeholder" />
+
+    <com.android.settings.widget.ValidatedEditTextPreference
+        android:key="wifi_tether_network_password"
+        android:title="@string/wifi_password" />
+
+    <ListPreference
+        android:key="wifi_tether_network_ap_band"
+        android:title="@string/wifi_ap_band_config"
+        android:summary="@string/summary_placeholder" />
+</PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java
index 708e17f..664916a 100644
--- a/src/com/android/settings/TetherSettings.java
+++ b/src/com/android/settings/TetherSettings.java
@@ -43,6 +43,8 @@
 import com.android.settings.datausage.DataSaverBackend;
 import com.android.settings.wifi.WifiApDialog;
 import com.android.settings.wifi.WifiApEnabler;
+import com.android.settings.wifi.tether.WifiTetherPreferenceController;
+import com.android.settings.wifi.tether.WifiTetherSettings;
 import com.android.settingslib.TetherUtil;
 
 import java.lang.ref.WeakReference;
@@ -63,7 +65,6 @@
     private static final String USB_TETHER_SETTINGS = "usb_tether_settings";
     private static final String ENABLE_WIFI_AP = "enable_wifi_ap";
     private static final String ENABLE_BLUETOOTH_TETHERING = "enable_bluetooth_tethering";
-    private static final String TETHER_CHOICE = "TETHER_TYPE";
     private static final String DATA_SAVER_FOOTER = "disabled_on_data_saver";
 
     private static final int DIALOG_AP_SETTINGS = 1;
@@ -100,17 +101,14 @@
     private WifiConfiguration mWifiConfig = null;
     private ConnectivityManager mCm;
 
+    private WifiTetherPreferenceController mWifiTetherPreferenceController;
+
     private boolean mRestartWifiApAfterConfigChange;
 
     private boolean mUsbConnected;
     private boolean mMassStorageActive;
 
     private boolean mBluetoothEnableForTether;
-
-    /* Stores the package name and the class name of the provisioning app */
-    private String[] mProvisionApp;
-    private static final int PROVISION_REQUEST = 0;
-
     private boolean mUnavailable;
 
     private DataSaverBackend mDataSaverBackend;
@@ -127,6 +125,13 @@
     }
 
     @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mWifiTetherPreferenceController =
+                new WifiTetherPreferenceController(context, getLifecycle());
+    }
+
+    @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
@@ -154,6 +159,7 @@
 
         mEnableWifiAp =
                 (SwitchPreference) findPreference(ENABLE_WIFI_AP);
+
         Preference wifiApSettings = findPreference(WIFI_AP_SSID_AND_SECURITY);
         mUsbTether = (SwitchPreference) findPreference(USB_TETHER_SETTINGS);
         mBluetoothTether = (SwitchPreference) findPreference(ENABLE_BLUETOOTH_TETHERING);
@@ -175,12 +181,18 @@
             getPreferenceScreen().removePreference(mUsbTether);
         }
 
-        if (wifiAvailable && !Utils.isMonkeyRunning()) {
-            mWifiApEnabler = new WifiApEnabler(activity, mDataSaverBackend, mEnableWifiAp);
-            initWifiTethering();
+        mWifiTetherPreferenceController.displayPreference(getPreferenceScreen());
+        if (WifiTetherSettings.isTetherSettingPageEnabled()) {
+            removePreference(ENABLE_WIFI_AP);
+            removePreference(WIFI_AP_SSID_AND_SECURITY);
         } else {
-            getPreferenceScreen().removePreference(mEnableWifiAp);
-            getPreferenceScreen().removePreference(wifiApSettings);
+            if (wifiAvailable && !Utils.isMonkeyRunning()) {
+                mWifiApEnabler = new WifiApEnabler(activity, mDataSaverBackend, mEnableWifiAp);
+                initWifiTethering();
+            } else {
+                getPreferenceScreen().removePreference(mEnableWifiAp);
+                getPreferenceScreen().removePreference(wifiApSettings);
+            }
         }
 
         if (!bluetoothAvailable) {
diff --git a/src/com/android/settings/widget/ValidatedEditTextPreference.java b/src/com/android/settings/widget/ValidatedEditTextPreference.java
new file mode 100644
index 0000000..53ff37a
--- /dev/null
+++ b/src/com/android/settings/widget/ValidatedEditTextPreference.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.EditText;
+
+import com.android.settings.CustomEditTextPreference;
+
+/**
+ * {@code EditTextPreference} that supports input validation.
+ */
+public class ValidatedEditTextPreference extends CustomEditTextPreference {
+
+    public interface Validator {
+        boolean isTextValid(String value);
+    }
+
+    private final EditTextWatcher mTextWatcher = new EditTextWatcher();
+    private Validator mValidator;
+    private boolean mIsPassword;
+
+    public ValidatedEditTextPreference(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public ValidatedEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public ValidatedEditTextPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ValidatedEditTextPreference(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+        if (mValidator != null) {
+            final EditText editText = view.findViewById(android.R.id.edit);
+            if (editText != null) {
+                editText.removeTextChangedListener(mTextWatcher);
+                if (mIsPassword) {
+                    editText.setInputType(
+                            InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+                    editText.setMaxLines(1);
+                }
+                editText.addTextChangedListener(mTextWatcher);
+            }
+        }
+    }
+
+    public void setIsPassword(boolean isPassword) {
+        mIsPassword = isPassword;
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+    public boolean isPassword() {
+        return mIsPassword;
+    }
+
+    public void setValidator(Validator validator) {
+        mValidator = validator;
+    }
+
+    private class EditTextWatcher implements TextWatcher {
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+        }
+
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int before, int count) {
+        }
+
+        @Override
+        public void afterTextChanged(Editable s) {
+            final EditText editText = getEditText();
+            if (mValidator != null && editText != null) {
+                final AlertDialog dialog = (AlertDialog) getDialog();
+                final boolean valid = mValidator.isTextValid(editText.getText().toString());
+                dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(valid);
+            }
+        }
+    }
+
+}
diff --git a/src/com/android/settings/wifi/WifiApEnabler.java b/src/com/android/settings/wifi/WifiApEnabler.java
index 5d725d8..675bf28 100644
--- a/src/com/android/settings/wifi/WifiApEnabler.java
+++ b/src/com/android/settings/wifi/WifiApEnabler.java
@@ -32,6 +32,10 @@
 
 import java.util.ArrayList;
 
+/**
+ * @deprecated in favor of WifiTetherPreferenceController and WifiTetherSettings
+ */
+@Deprecated
 public class WifiApEnabler {
     private final Context mContext;
     private final SwitchPreference mSwitch;
diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java
index 2a17dfc..6f87342 100644
--- a/src/com/android/settings/wifi/WifiConfigController.java
+++ b/src/com/android/settings/wifi/WifiConfigController.java
@@ -110,7 +110,6 @@
     public static final int WIFI_PEAP_PHASE2_AKA        = 4;
     public static final int WIFI_PEAP_PHASE2_AKA_PRIME  = 5;
 
-    private static final int SSID_ASCII_MAX_LENGTH = 32;
 
     /* Phase2 methods supported by PEAP are limited */
     private final ArrayAdapter<String> mPhase2PeapAdapter;
@@ -463,7 +462,7 @@
 
         if (mSsidView != null) {
             final String ssid = mSsidView.getText().toString();
-            if (ssid.length() > SSID_ASCII_MAX_LENGTH) {
+            if (WifiUtils.isSSIDTooLong(ssid)) {
                 mView.findViewById(R.id.ssid_too_long_warning).setVisibility(View.VISIBLE);
             }
         }
diff --git a/src/com/android/settings/wifi/WifiUtils.java b/src/com/android/settings/wifi/WifiUtils.java
new file mode 100644
index 0000000..7bd69db
--- /dev/null
+++ b/src/com/android/settings/wifi/WifiUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi;
+
+import android.text.TextUtils;
+
+public class WifiUtils {
+
+    private static final int SSID_ASCII_MIN_LENGTH = 1;
+    private static final int SSID_ASCII_MAX_LENGTH = 32;
+    private static final int PASSWORD_MIN_LENGTH = 8;
+    private static final int PASSWORD_MAX_LENGTH = 63;
+
+
+    public static boolean isSSIDTooLong(String ssid) {
+        if (TextUtils.isEmpty(ssid)) {
+            return false;
+        }
+        return ssid.length() > SSID_ASCII_MAX_LENGTH;
+    }
+
+    public static boolean isSSIDTooShort(String ssid) {
+        if (TextUtils.isEmpty(ssid)) {
+            return true;
+        }
+        return ssid.length() < SSID_ASCII_MIN_LENGTH;
+    }
+
+    public static boolean isPasswordValid(String password) {
+        if (TextUtils.isEmpty(password)) {
+            return false;
+        }
+        final int length = password.length();
+        return length >= PASSWORD_MIN_LENGTH && length <= PASSWORD_MAX_LENGTH;
+    }
+}
diff --git a/src/com/android/settings/wifi/tether/NoOpOnStartTetheringCallback.java b/src/com/android/settings/wifi/tether/NoOpOnStartTetheringCallback.java
new file mode 100644
index 0000000..fc1719c
--- /dev/null
+++ b/src/com/android/settings/wifi/tether/NoOpOnStartTetheringCallback.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.net.ConnectivityManager;
+
+class NoOpOnStartTetheringCallback extends ConnectivityManager.OnStartTetheringCallback {
+
+}
diff --git a/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceController.java
new file mode 100644
index 0000000..37da38e
--- /dev/null
+++ b/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceController.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.support.v7.preference.ListPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.R;
+
+import static android.net.wifi.WifiConfiguration.AP_BAND_2GHZ;
+import static android.net.wifi.WifiConfiguration.AP_BAND_5GHZ;
+
+public class WifiTetherApBandPreferenceController extends WifiTetherBasePreferenceController {
+
+    private static final String PREF_KEY = "wifi_tether_network_ap_band";
+    private static final String[] BAND_VALUES =
+            {String.valueOf(AP_BAND_2GHZ), String.valueOf(AP_BAND_5GHZ)};
+
+    private final String[] mBandEntries;
+    private int mBandIndex;
+
+    public WifiTetherApBandPreferenceController(Context context,
+            OnTetherConfigUpdateListener listener) {
+        super(context, listener);
+        mBandEntries = mContext.getResources().getStringArray(R.array.wifi_ap_band_config_full);
+        final WifiConfiguration config = mWifiManager.getWifiApConfiguration();
+        if (config != null) {
+            mBandIndex = config.apBand;
+        } else {
+            mBandIndex = 0;
+        }
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        ListPreference preference = (ListPreference) mPreference;
+        if (!is5GhzBandSupported()) {
+            preference.setEnabled(false);
+            preference.setSummary(R.string.wifi_ap_choose_2G);
+        } else {
+            preference.setEntries(mBandEntries);
+            preference.setEntryValues(BAND_VALUES);
+            preference.setSummary(mBandEntries[mBandIndex]);
+            preference.setValue(String.valueOf(mBandIndex));
+        }
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return PREF_KEY;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        mBandIndex = Integer.parseInt((String) newValue);
+        preference.setSummary(mBandEntries[mBandIndex]);
+        mListener.onTetherConfigUpdated();
+        return true;
+    }
+
+    private boolean is5GhzBandSupported() {
+        if (mBandIndex > 0) {
+            return true;
+        }
+        return mWifiManager.is5GHzBandSupported();
+    }
+
+    public int getBandIndex() {
+        return mBandIndex;
+    }
+}
diff --git a/src/com/android/settings/wifi/tether/WifiTetherBasePreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherBasePreferenceController.java
new file mode 100644
index 0000000..eb21175
--- /dev/null
+++ b/src/com/android/settings/wifi/tether/WifiTetherBasePreferenceController.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.core.PreferenceController;
+
+public abstract class WifiTetherBasePreferenceController extends PreferenceController
+        implements Preference.OnPreferenceChangeListener {
+
+    public interface OnTetherConfigUpdateListener {
+        void onTetherConfigUpdated();
+    }
+
+    protected final WifiManager mWifiManager;
+    protected final String[] mWifiRegexs;
+    protected final ConnectivityManager mCm;
+    protected final OnTetherConfigUpdateListener mListener;
+
+    protected Preference mPreference;
+
+    public WifiTetherBasePreferenceController(Context context,
+            OnTetherConfigUpdateListener listener) {
+        super(context);
+        mListener = listener;
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        mWifiRegexs = mCm.getTetherableWifiRegexs();
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mWifiManager != null && mWifiRegexs != null && mWifiRegexs.length > 0;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mPreference = screen.findPreference(getPreferenceKey());
+    }
+}
diff --git a/src/com/android/settings/wifi/tether/WifiTetherPasswordPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherPasswordPreferenceController.java
new file mode 100644
index 0000000..a929b86
--- /dev/null
+++ b/src/com/android/settings/wifi/tether/WifiTetherPasswordPreferenceController.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.widget.ValidatedEditTextPreference;
+import com.android.settings.wifi.WifiUtils;
+
+public class WifiTetherPasswordPreferenceController extends WifiTetherBasePreferenceController
+        implements ValidatedEditTextPreference.Validator {
+
+    private static final String PREF_KEY = "wifi_tether_network_password";
+
+    private String mPassword;
+
+    public WifiTetherPasswordPreferenceController(Context context,
+            OnTetherConfigUpdateListener listener) {
+        super(context, listener);
+        final WifiConfiguration config = mWifiManager.getWifiApConfiguration();
+        if (config != null) {
+            mPassword = config.preSharedKey;
+        }
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return PREF_KEY;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        ((ValidatedEditTextPreference) mPreference).setText(mPassword);
+        ((ValidatedEditTextPreference) mPreference).setIsPassword(true);
+        ((ValidatedEditTextPreference) mPreference).setValidator(this);
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        mPassword = (String) newValue;
+        ((ValidatedEditTextPreference) mPreference).setText(mPassword);
+        mListener.onTetherConfigUpdated();
+        return true;
+    }
+
+    public String getPassword() {
+        return mPassword;
+    }
+
+    @Override
+    public boolean isTextValid(String value) {
+        return WifiUtils.isPasswordValid(value);
+    }
+}
diff --git a/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java
new file mode 100644
index 0000000..46fb7a9
--- /dev/null
+++ b/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.provider.Settings;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.BidiFormatter;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.core.PreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+
+import java.util.List;
+
+public class WifiTetherPreferenceController extends PreferenceController
+        implements LifecycleObserver, OnResume, OnPause {
+
+    public static final IntentFilter WIFI_TETHER_INTENT_FILTER;
+    private static final String WIFI_TETHER_SETTINGS = "wifi_tether";
+
+    private final ConnectivityManager mConnectivityManager;
+    private final String[] mWifiRegexs;
+    private final WifiManager mWifiManager;
+    private Preference mPreference;
+
+    static {
+        WIFI_TETHER_INTENT_FILTER = new IntentFilter(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        WIFI_TETHER_INTENT_FILTER.addAction(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
+        WIFI_TETHER_INTENT_FILTER.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+    }
+
+    public WifiTetherPreferenceController(Context context, Lifecycle lifecycle) {
+        super(context);
+        mConnectivityManager =
+                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+
+        mWifiRegexs = mConnectivityManager.getTetherableWifiRegexs();
+
+        if (lifecycle != null) {
+            lifecycle.addObserver(this);
+        }
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mWifiRegexs != null
+                && mWifiRegexs.length != 0
+                && WifiTetherSettings.isTetherSettingPageEnabled()
+                && !Utils.isMonkeyRunning();
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mPreference = screen.findPreference(WIFI_TETHER_SETTINGS);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return WIFI_TETHER_SETTINGS;
+    }
+
+    @Override
+    public void onResume() {
+        if (mPreference != null) {
+            mContext.registerReceiver(mReceiver, WIFI_TETHER_INTENT_FILTER);
+            clearSummaryForAirplaneMode();
+        }
+    }
+
+    @Override
+    public void onPause() {
+        if (mPreference != null) {
+            mContext.unregisterReceiver(mReceiver);
+        }
+    }
+
+    //
+    // Everything below is copied from WifiApEnabler
+    //
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(action)) {
+                int state = intent.getIntExtra(
+                        WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED);
+                int reason = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON,
+                        WifiManager.SAP_START_FAILURE_GENERAL);
+                handleWifiApStateChanged(state, reason);
+            } else if (ConnectivityManager.ACTION_TETHER_STATE_CHANGED.equals(action)) {
+                List<String> active = intent.getStringArrayListExtra(
+                        ConnectivityManager.EXTRA_ACTIVE_TETHER);
+                List<String> errored = intent.getStringArrayListExtra(
+                        ConnectivityManager.EXTRA_ERRORED_TETHER);
+                updateTetherState(active.toArray(), errored.toArray());
+            } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
+                clearSummaryForAirplaneMode();
+            }
+        }
+    };
+
+    private void handleWifiApStateChanged(int state, int reason) {
+        switch (state) {
+            case WifiManager.WIFI_AP_STATE_ENABLED:
+                /**
+                 * Summary on enable is handled by tether
+                 * broadcast notice
+                 */
+                break;
+            case WifiManager.WIFI_AP_STATE_DISABLING:
+                mPreference.setSummary(R.string.wifi_tether_stopping);
+                break;
+            case WifiManager.WIFI_AP_STATE_DISABLED:
+                mPreference.setSummary(R.string.wifi_hotspot_off_subtext);
+                clearSummaryForAirplaneMode();
+                break;
+            default:
+                if (reason == WifiManager.SAP_START_FAILURE_NO_CHANNEL) {
+                    mPreference.setSummary(R.string.wifi_sap_no_channel_error);
+                } else {
+                    mPreference.setSummary(R.string.wifi_error);
+                }
+                clearSummaryForAirplaneMode();
+        }
+    }
+
+    private void updateTetherState(Object[] tethered, Object[] errored) {
+        boolean wifiTethered = matchRegex(tethered);
+        boolean wifiErrored = matchRegex(errored);
+
+        if (wifiTethered) {
+            WifiConfiguration wifiConfig = mWifiManager.getWifiApConfiguration();
+            updateConfigSummary(wifiConfig);
+        } else if (wifiErrored) {
+            mPreference.setSummary(R.string.wifi_error);
+        } else {
+            mPreference.setSummary(R.string.wifi_hotspot_off_subtext);
+        }
+    }
+
+    private boolean matchRegex(Object[] tethers) {
+        for (Object o : tethers) {
+            String s = (String) o;
+            for (String regex : mWifiRegexs) {
+                if (s.matches(regex)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void updateConfigSummary(WifiConfiguration wifiConfig) {
+        final String s = mContext.getString(
+                com.android.internal.R.string.wifi_tether_configure_ssid_default);
+
+        mPreference.setSummary(mContext.getString(R.string.wifi_tether_enabled_subtext,
+                BidiFormatter.getInstance().unicodeWrap(
+                        (wifiConfig == null) ? s : wifiConfig.SSID)));
+    }
+
+    private void clearSummaryForAirplaneMode() {
+        boolean isAirplaneMode = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+        if (isAirplaneMode) {
+            mPreference.setSummary(R.string.summary_placeholder);
+        }
+    }
+    //
+    // Everything above is copied from WifiApEnabler
+    //
+}
diff --git a/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java
new file mode 100644
index 0000000..a4c6c67
--- /dev/null
+++ b/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.EditTextPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.widget.ValidatedEditTextPreference;
+import com.android.settings.wifi.WifiUtils;
+
+public class WifiTetherSSIDPreferenceController extends WifiTetherBasePreferenceController
+        implements ValidatedEditTextPreference.Validator {
+
+    private static final String PREF_KEY = "wifi_tether_network_name";
+    @VisibleForTesting
+    static final String DEFAULT_SSID = "AndroidAP";
+
+    private String mSSID;
+
+    public WifiTetherSSIDPreferenceController(Context context,
+            OnTetherConfigUpdateListener listener) {
+        super(context, listener);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return PREF_KEY;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        final WifiConfiguration config = mWifiManager.getWifiApConfiguration();
+        if (config != null) {
+            mSSID = config.SSID;
+        } else {
+            mSSID = DEFAULT_SSID;
+        }
+        ((ValidatedEditTextPreference) mPreference).setValidator(this);
+        updateSsidDisplay((EditTextPreference) mPreference);
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        mSSID = (String) newValue;
+        updateSsidDisplay((EditTextPreference) preference);
+        mListener.onTetherConfigUpdated();
+        return true;
+    }
+
+    @Override
+    public boolean isTextValid(String value) {
+        return !WifiUtils.isSSIDTooLong(value) && !WifiUtils.isSSIDTooShort(value);
+    }
+
+    public String getSSID() {
+        return mSSID;
+    }
+
+    private void updateSsidDisplay(EditTextPreference preference) {
+        preference.setText(mSSID);
+        preference.setSummary(mSSID);
+    }
+}
diff --git a/src/com/android/settings/wifi/tether/WifiTetherSettings.java b/src/com/android/settings/wifi/tether/WifiTetherSettings.java
new file mode 100644
index 0000000..2584ed4
--- /dev/null
+++ b/src/com/android/settings/wifi/tether/WifiTetherSettings.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.dashboard.RestrictedDashboardFragment;
+import com.android.settings.widget.SwitchBar;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_CHANGED_ACTION;
+
+public class WifiTetherSettings extends RestrictedDashboardFragment
+        implements WifiTetherBasePreferenceController.OnTetherConfigUpdateListener {
+
+    public static boolean isTetherSettingPageEnabled() {
+        return SystemProperties.getBoolean("settings.ui.wifi.tether.enabled", true);
+    }
+
+    private static final IntentFilter TETHER_STATE_CHANGE_FILTER;
+
+    private WifiTetherSwitchBarController mSwitchBarController;
+    private WifiTetherSSIDPreferenceController mSSIDPreferenceController;
+    private WifiTetherPasswordPreferenceController mPasswordPreferenceController;
+    private WifiTetherApBandPreferenceController mApBandPreferenceController;
+
+    private WifiManager mWifiManager;
+    private boolean mRestartWifiApAfterConfigChange;
+
+    @VisibleForTesting
+    TetherChangeReceiver mTetherChangeReceiver;
+
+    static {
+        TETHER_STATE_CHANGE_FILTER = new IntentFilter(ACTION_TETHER_STATE_CHANGED);
+        TETHER_STATE_CHANGE_FILTER.addAction(WIFI_AP_STATE_CHANGED_ACTION);
+    }
+
+    public WifiTetherSettings() {
+        super(UserManager.DISALLOW_CONFIG_TETHERING);
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.WIFI_TETHER_SETTINGS;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return "WifiTetherSettings";
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        mTetherChangeReceiver = new TetherChangeReceiver();
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        // 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();
+        final SwitchBar switchBar = activity.getSwitchBar();
+        mSwitchBarController = new WifiTetherSwitchBarController(activity, switchBar);
+        getLifecycle().addObserver(mSwitchBarController);
+        switchBar.show();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        final Context context = getContext();
+        if (context != null) {
+            context.registerReceiver(mTetherChangeReceiver, TETHER_STATE_CHANGE_FILTER);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        final Context context = getContext();
+        if (context != null) {
+            context.unregisterReceiver(mTetherChangeReceiver);
+        }
+    }
+
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.wifi_tether_settings;
+    }
+
+    @Override
+    protected List<PreferenceController> getPreferenceControllers(Context context) {
+        final List<PreferenceController> controllers = new ArrayList<>();
+        mSSIDPreferenceController = new WifiTetherSSIDPreferenceController(context, this);
+        mPasswordPreferenceController = new WifiTetherPasswordPreferenceController(context, this);
+        mApBandPreferenceController = new WifiTetherApBandPreferenceController(context, this);
+
+        controllers.add(mSSIDPreferenceController);
+        controllers.add(mPasswordPreferenceController);
+        controllers.add(mApBandPreferenceController);
+        return controllers;
+    }
+
+    @Override
+    public void onTetherConfigUpdated() {
+        final WifiConfiguration config = buildNewConfig();
+        /**
+         * if soft AP is stopped, bring up
+         * else restart with new config
+         * TODO: update config on a running access point when framework support is added
+         */
+        if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) {
+            Log.d("TetheringSettings",
+                    "Wifi AP config changed while enabled, stop and restart");
+            mRestartWifiApAfterConfigChange = true;
+            mSwitchBarController.stopTether();
+        }
+        mWifiManager.setWifiApConfiguration(config);
+    }
+
+    private WifiConfiguration buildNewConfig() {
+        final WifiConfiguration config = new WifiConfiguration();
+
+        config.SSID = mSSIDPreferenceController.getSSID();
+        config.preSharedKey = mPasswordPreferenceController.getPassword();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
+        config.apBand = mApBandPreferenceController.getBandIndex();
+        return config;
+    }
+
+    @VisibleForTesting
+    class TetherChangeReceiver extends BroadcastReceiver {
+        private static final String TAG = "TetherChangeReceiver";
+
+        @Override
+        public void onReceive(Context content, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(ACTION_TETHER_STATE_CHANGED)) {
+                if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_DISABLED
+                        && mRestartWifiApAfterConfigChange) {
+                    mRestartWifiApAfterConfigChange = false;
+                    Log.d(TAG, "Restarting WifiAp due to prior config change.");
+                    mSwitchBarController.startTether();
+                }
+            } else if (action.equals(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;
+                    Log.d(TAG, "Restarting WifiAp due to prior config change.");
+                    mSwitchBarController.startTether();
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java b/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java
new file mode 100644
index 0000000..e3c6098
--- /dev/null
+++ b/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.widget.Switch;
+
+import com.android.settings.datausage.DataSaverBackend;
+import com.android.settings.widget.SwitchBar;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+import static android.net.ConnectivityManager.TETHERING_WIFI;
+
+public class WifiTetherSwitchBarController implements SwitchBar.OnSwitchChangeListener,
+        LifecycleObserver, OnStart, OnStop {
+
+    private final Context mContext;
+    private final SwitchBar mSwitchBar;
+    private final ConnectivityManager mConnectivityManager;
+    private final DataSaverBackend mDataSaverBackend;
+    private final NoOpOnStartTetheringCallback mOnStartTetheringCallback;
+
+    WifiTetherSwitchBarController(Context context, SwitchBar switchBar) {
+        mContext = context;
+        mSwitchBar = switchBar;
+        mDataSaverBackend = new DataSaverBackend(context);
+        mOnStartTetheringCallback = new NoOpOnStartTetheringCallback();
+        mConnectivityManager =
+                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        mSwitchBar.addOnSwitchChangeListener(this);
+    }
+
+    @Override
+    public void onStart() {
+        mContext.registerReceiver(mReceiver,
+                WifiTetherPreferenceController.WIFI_TETHER_INTENT_FILTER);
+    }
+
+    @Override
+    public void onStop() {
+        mContext.unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    public void onSwitchChanged(Switch switchView, boolean isChecked) {
+        if (isChecked) {
+            startTether();
+        } else {
+            stopTether();
+        }
+    }
+
+    void stopTether() {
+        mSwitchBar.setEnabled(false);
+        mConnectivityManager.stopTethering(TETHERING_WIFI);
+    }
+
+    void startTether() {
+        mSwitchBar.setEnabled(false);
+        mConnectivityManager.startTethering(TETHERING_WIFI, true /* showProvisioningUi */,
+                mOnStartTetheringCallback, new Handler(Looper.getMainLooper()));
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(action)) {
+                final int state = intent.getIntExtra(
+                        WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED);
+                handleWifiApStateChanged(state);
+            } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
+                enableWifiSwitch();
+            }
+        }
+    };
+
+    private void handleWifiApStateChanged(int state) {
+        switch (state) {
+            case WifiManager.WIFI_AP_STATE_ENABLING:
+                mSwitchBar.setEnabled(false);
+                break;
+            case WifiManager.WIFI_AP_STATE_ENABLED:
+                if (!mSwitchBar.isChecked()) {
+                    mSwitchBar.setChecked(true);
+                }
+                enableWifiSwitch();
+                break;
+            case WifiManager.WIFI_AP_STATE_DISABLING:
+                if (mSwitchBar.isChecked()) {
+                    mSwitchBar.setChecked(false);
+                }
+                mSwitchBar.setEnabled(false);
+                break;
+            case WifiManager.WIFI_AP_STATE_DISABLED:
+                mSwitchBar.setChecked(false);
+                enableWifiSwitch();
+                break;
+            default:
+                mSwitchBar.setChecked(false);
+                enableWifiSwitch();
+                break;
+        }
+    }
+
+    private void enableWifiSwitch() {
+        boolean isAirplaneMode = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+        if (!isAirplaneMode) {
+            mSwitchBar.setEnabled(!mDataSaverBackend.isDataSaverEnabled());
+        } else {
+            mSwitchBar.setEnabled(false);
+        }
+    }
+}
diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider
index b9e328c..d901a11 100644
--- a/tests/robotests/assets/grandfather_not_implementing_index_provider
+++ b/tests/robotests/assets/grandfather_not_implementing_index_provider
@@ -13,3 +13,4 @@
 com.android.settings.enterprise.ApplicationListFragment$AdminGrantedPermissionMicrophone
 com.android.settings.enterprise.ApplicationListFragment$EnterpriseInstalledPackages
 com.android.settings.enterprise.EnterpriseSetDefaultAppsListFragment
+com.android.settings.wifi.tether.WifiTetherSettings
diff --git a/tests/robotests/src/com/android/settings/core/codeinspection/CodeInspectionTest.java b/tests/robotests/src/com/android/settings/core/codeinspection/CodeInspectionTest.java
index 23e1e2e..d05bee6 100644
--- a/tests/robotests/src/com/android/settings/core/codeinspection/CodeInspectionTest.java
+++ b/tests/robotests/src/com/android/settings/core/codeinspection/CodeInspectionTest.java
@@ -16,10 +16,10 @@
 
 package com.android.settings.core.codeinspection;
 
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.core.instrumentation.InstrumentableFragmentCodeInspector;
 import com.android.settings.search.SearchIndexProviderCodeInspector;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/robotests/src/com/android/settings/widget/ValidatedEditTextPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/ValidatedEditTextPreferenceTest.java
new file mode 100644
index 0000000..88a5147
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/widget/ValidatedEditTextPreferenceTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.EditText;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ValidatedEditTextPreferenceTest {
+
+    @Mock
+    private View mView;
+    @Mock
+    private ValidatedEditTextPreference.Validator mValidator;
+
+    private ValidatedEditTextPreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mPreference = new ValidatedEditTextPreference(RuntimeEnvironment.application);
+    }
+
+    @Test
+    public void bindDialogView_noTextWatcher_shouldDoNothing() {
+        mPreference.onBindDialogView(mView);
+
+        verifyZeroInteractions(mView);
+    }
+
+    @Test
+    public void bindDialogView_hasValidator_shouldBindToEditText() {
+        final EditText editText = spy(new EditText(RuntimeEnvironment.application));
+        when(mView.findViewById(android.R.id.edit)).thenReturn(editText);
+
+        mPreference.setValidator(mValidator);
+        mPreference.onBindDialogView(mView);
+
+        verify(editText).addTextChangedListener(any(TextWatcher.class));
+    }
+
+    @Test
+    public void bindDialogView_isPassword_shouldSetInputType() {
+        final EditText editText = spy(new EditText(RuntimeEnvironment.application));
+        when(mView.findViewById(android.R.id.edit)).thenReturn(editText);
+
+        mPreference.setValidator(mValidator);
+        mPreference.setIsPassword(true);
+        mPreference.onBindDialogView(mView);
+
+        assertThat(editText.getInputType()
+                & (InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_CLASS_TEXT))
+                .isNotEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/WifiUtilsTest.java b/tests/robotests/src/com/android/settings/wifi/WifiUtilsTest.java
new file mode 100644
index 0000000..1ccdb1f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/WifiUtilsTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi;
+
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WifiUtilsTest {
+
+    @Test
+    public void testSSID() {
+        assertThat(WifiUtils.isSSIDTooLong("123")).isFalse();
+        assertThat(WifiUtils.isSSIDTooLong("☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎☎")).isTrue();
+
+        assertThat(WifiUtils.isSSIDTooShort("123")).isFalse();
+        assertThat(WifiUtils.isSSIDTooShort("")).isTrue();
+    }
+
+    @Test
+    public void testPassword() {
+        final String longPassword = "123456789012345678901234567890"
+                + "1234567890123456789012345678901234567890";
+        assertThat(WifiUtils.isPasswordValid("123")).isFalse();
+        assertThat(WifiUtils.isPasswordValid("12345678")).isTrue();
+        assertThat(WifiUtils.isPasswordValid("1234567890")).isTrue();
+        assertThat(WifiUtils.isPasswordValid(longPassword)).isFalse();
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceControllerTest.java
new file mode 100644
index 0000000..a7e00ab
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherApBandPreferenceControllerTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.support.v7.preference.ListPreference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WifiTetherApBandPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private ConnectivityManager mConnectivityManager;
+    @Mock
+    private WifiManager mWifiManager;
+    @Mock
+    private WifiTetherBasePreferenceController.OnTetherConfigUpdateListener mListener;
+    @Mock
+    private PreferenceScreen mScreen;
+
+    private WifiTetherApBandPreferenceController mController;
+    private ListPreference mListPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mListPreference = new ListPreference(RuntimeEnvironment.application);
+        when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager);
+        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
+                .thenReturn(mConnectivityManager);
+        when(mConnectivityManager.getTetherableWifiRegexs()).thenReturn(new String[]{"1", "2"});
+        when(mContext.getResources()).thenReturn(RuntimeEnvironment.application.getResources());
+        when(mScreen.findPreference(anyString())).thenReturn(mListPreference);
+
+        mController = new WifiTetherApBandPreferenceController(mContext, mListener);
+    }
+
+    @Test
+    public void display_5GhzSupported_shouldDisplayFullList() {
+        when(mWifiManager.is5GHzBandSupported()).thenReturn(true);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mListPreference.getEntries().length).isEqualTo(2);
+    }
+
+    @Test
+    public void display_5GhzNotSupported_shouldDisable() {
+        when(mWifiManager.is5GHzBandSupported()).thenReturn(false);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mListPreference.getEntries()).isNull();
+        assertThat(mListPreference.isEnabled()).isFalse();
+        assertThat(mListPreference.getSummary())
+                .isEqualTo(RuntimeEnvironment.application.getString(R.string.wifi_ap_choose_2G));
+    }
+
+    @Test
+    public void changePreference_shouldUpdateValue() {
+        when(mWifiManager.is5GHzBandSupported()).thenReturn(true);
+
+        mController.displayPreference(mScreen);
+        mController.onPreferenceChange(mListPreference, "1");
+        assertThat(mController.getBandIndex()).isEqualTo(1);
+
+        mController.onPreferenceChange(mListPreference, "0");
+        assertThat(mController.getBandIndex()).isEqualTo(0);
+
+        verify(mListener, times(2)).onTetherConfigUpdated();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPasswordPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPasswordPreferenceControllerTest.java
new file mode 100644
index 0000000..7ea2ea9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPasswordPreferenceControllerTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.widget.ValidatedEditTextPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WifiTetherPasswordPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private ConnectivityManager mConnectivityManager;
+    @Mock
+    private WifiManager mWifiManager;
+    @Mock
+    private WifiTetherBasePreferenceController.OnTetherConfigUpdateListener mListener;
+    @Mock
+    private PreferenceScreen mScreen;
+
+    private WifiTetherPasswordPreferenceController mController;
+    private ValidatedEditTextPreference mPreference;
+    private WifiConfiguration mConfig;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mPreference = new ValidatedEditTextPreference(RuntimeEnvironment.application);
+        mConfig = new WifiConfiguration();
+        mConfig.SSID = "test_1234";
+        mConfig.preSharedKey = "test_password";
+
+        when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager);
+        when(mWifiManager.getWifiApConfiguration()).thenReturn(mConfig);
+        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
+                .thenReturn(mConnectivityManager);
+        when(mConnectivityManager.getTetherableWifiRegexs()).thenReturn(new String[]{"1", "2"});
+        when(mContext.getResources()).thenReturn(RuntimeEnvironment.application.getResources());
+        when(mScreen.findPreference(anyString())).thenReturn(mPreference);
+
+        mController = new WifiTetherPasswordPreferenceController(mContext, mListener);
+    }
+
+    @Test
+    public void displayPreference_shouldStylePreference() {
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.getText()).isEqualTo(mConfig.preSharedKey);
+        assertThat(mPreference.isPassword()).isTrue();
+    }
+
+    @Test
+    public void changePreference_shouldUpdateValue() {
+        mController.displayPreference(mScreen);
+        mController.onPreferenceChange(mPreference, "1");
+        assertThat(mController.getPassword()).isEqualTo("1");
+
+        mController.onPreferenceChange(mPreference, "0");
+        assertThat(mController.getPassword()).isEqualTo("0");
+
+        verify(mListener, times(2)).onTetherConfigUpdated();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java
new file mode 100644
index 0000000..c3bc1eb
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.provider.Settings;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowSettings;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+        shadows = {
+                WifiTetherPreferenceControllerTest.ShadowWifiTetherSettings.class
+        })
+public class WifiTetherPreferenceControllerTest {
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private ConnectivityManager mConnectivityManager;
+    @Mock
+    private WifiManager mWifiManager;
+    @Mock
+    private PreferenceScreen mScreen;
+
+    private WifiTetherPreferenceController mController;
+    private Lifecycle mLifecycle;
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLifecycle = new Lifecycle();
+        mPreference = new Preference(RuntimeEnvironment.application);
+        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
+                .thenReturn(mConnectivityManager);
+        when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager);
+        when(mScreen.findPreference(anyString())).thenReturn(mPreference);
+
+        when(mConnectivityManager.getTetherableWifiRegexs()).thenReturn(new String[]{"1", "2"});
+        mController = new WifiTetherPreferenceController(mContext, mLifecycle);
+    }
+
+    @Test
+    public void isAvailable_noTetherRegex_shouldReturnFalse() {
+        when(mConnectivityManager.getTetherableWifiRegexs()).thenReturn(new String[]{});
+        mController = new WifiTetherPreferenceController(mContext, mLifecycle);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_hasTetherRegex_shouldReturnTrue() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void resumeAndPause_shouldRegisterUnregisterReceiver() {
+        final BroadcastReceiver receiver = ReflectionHelpers.getField(mController, "mReceiver");
+
+        mController.displayPreference(mScreen);
+        mLifecycle.onResume();
+        mLifecycle.onPause();
+
+        verify(mContext).registerReceiver(eq(receiver), any(IntentFilter.class));
+        verify(mContext).unregisterReceiver(receiver);
+
+    }
+
+    @Test
+    public void testReceiver_apStateChangedToDisabled_shouldUpdatePreferenceSummary() {
+        mController.displayPreference(mScreen);
+        final BroadcastReceiver receiver = ReflectionHelpers.getField(mController, "mReceiver");
+        final Intent broadcast = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        broadcast.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_DISABLED);
+
+        receiver.onReceive(RuntimeEnvironment.application, broadcast);
+
+        assertThat(mPreference.getSummary().toString()).isEqualTo(
+                RuntimeEnvironment.application.getString(R.string.wifi_hotspot_off_subtext));
+    }
+
+    @Test
+    public void testReceiver_apStateChangedToDisabling_shouldUpdatePreferenceSummary() {
+        mController.displayPreference(mScreen);
+        final BroadcastReceiver receiver = ReflectionHelpers.getField(mController, "mReceiver");
+        final Intent broadcast = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        broadcast.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_DISABLING);
+
+        receiver.onReceive(RuntimeEnvironment.application, broadcast);
+
+        assertThat(mPreference.getSummary().toString()).isEqualTo(
+                RuntimeEnvironment.application.getString(R.string.wifi_tether_stopping));
+    }
+
+    @Test
+    public void testReceiver_goingToAirplaneMode_shouldClearPreferenceSummary() {
+        final ContentResolver cr = mock(ContentResolver.class);
+        when(mContext.getContentResolver()).thenReturn(cr);
+        ShadowSettings.ShadowGlobal.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, 1);
+        mController.displayPreference(mScreen);
+        final BroadcastReceiver receiver = ReflectionHelpers.getField(mController, "mReceiver");
+        final Intent broadcast = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+
+        receiver.onReceive(RuntimeEnvironment.application, broadcast);
+
+        assertThat(mPreference.getSummary().toString()).isEqualTo(
+                RuntimeEnvironment.application.getString(R.string.summary_placeholder));
+    }
+
+    @Test
+    public void testReceiver_tetherEnabled_shouldUpdatePreferenceSummary() {
+        mController.displayPreference(mScreen);
+        final BroadcastReceiver receiver = ReflectionHelpers.getField(mController, "mReceiver");
+        final Intent broadcast = new Intent(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
+        final ArrayList<String> activeTethers = new ArrayList<>();
+        activeTethers.add("1");
+        broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER, activeTethers);
+        broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER,
+                new ArrayList<>());
+        final WifiConfiguration configuration = new WifiConfiguration();
+        configuration.SSID = "test-ap";
+        when(mWifiManager.getWifiApConfiguration()).thenReturn(configuration);
+
+        receiver.onReceive(RuntimeEnvironment.application, broadcast);
+
+        verify(mContext).getString(eq(R.string.wifi_tether_enabled_subtext), any());
+    }
+
+    @Implements(WifiTetherSettings.class)
+    public static final class ShadowWifiTetherSettings {
+
+        @Implementation
+        public static boolean isTetherSettingPageEnabled() {
+            return true;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceControllerTest.java
new file mode 100644
index 0000000..f43e3a7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceControllerTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.widget.ValidatedEditTextPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WifiTetherSSIDPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private ConnectivityManager mConnectivityManager;
+    @Mock
+    private WifiManager mWifiManager;
+    @Mock
+    private WifiTetherBasePreferenceController.OnTetherConfigUpdateListener mListener;
+    @Mock
+    private PreferenceScreen mScreen;
+
+    private WifiTetherSSIDPreferenceController mController;
+    private ValidatedEditTextPreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mPreference = new ValidatedEditTextPreference(RuntimeEnvironment.application);
+
+        when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager);
+        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
+                .thenReturn(mConnectivityManager);
+        when(mConnectivityManager.getTetherableWifiRegexs()).thenReturn(new String[]{"1", "2"});
+        when(mContext.getResources()).thenReturn(RuntimeEnvironment.application.getResources());
+        when(mScreen.findPreference(anyString())).thenReturn(mPreference);
+
+        mController = new WifiTetherSSIDPreferenceController(mContext, mListener);
+    }
+
+    @Test
+    public void displayPreference_noWifiConfig_shouldDisplayDefaultSSID() {
+        when(mWifiManager.getWifiApConfiguration()).thenReturn(null);
+
+        mController.displayPreference(mScreen);
+        assertThat(mController.getSSID())
+                .isEqualTo(WifiTetherSSIDPreferenceController.DEFAULT_SSID);
+    }
+
+    @Test
+    public void displayPreference_hasCustomWifiConfig_shouldDisplayCustomSSID() {
+        final WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "test_1234";
+        when(mWifiManager.getWifiApConfiguration()).thenReturn(config);
+
+        mController.displayPreference(mScreen);
+        assertThat(mController.getSSID()).isEqualTo(config.SSID);
+    }
+
+    @Test
+    public void changePreference_shouldUpdateValue() {
+        mController.displayPreference(mScreen);
+        mController.onPreferenceChange(mPreference, "1");
+        assertThat(mController.getSSID()).isEqualTo("1");
+
+        mController.onPreferenceChange(mPreference, "0");
+        assertThat(mController.getSSID()).isEqualTo("0");
+
+        verify(mListener, times(2)).onTetherConfigUpdated();
+    }
+}
diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk
index f9c0489..060c3e1 100644
--- a/tests/unit/Android.mk
+++ b/tests/unit/Android.mk
@@ -9,10 +9,12 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
-    mockito-target-minus-junit4 \
     espresso-core \
+    legacy-android-test \
+    mockito-target-minus-junit4 \
     truth-prebuilt \
-    legacy-android-test
+    ub-uiautomator \
+
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/unit/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java b/tests/unit/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java
new file mode 100644
index 0000000..26a711b
--- /dev/null
+++ b/tests/unit/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.tether;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+import com.android.settings.Settings;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WifiTetherSettingsTest {
+
+    private static final long TIMEOUT = 2000L;
+
+    private Instrumentation mInstrumentation;
+    private Intent mTetherActivityIntent;
+    private UiDevice mDevice;
+
+    @Before
+    public void setUp() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mDevice = UiDevice.getInstance(mInstrumentation);
+        mTetherActivityIntent = new Intent()
+                .setClassName(mInstrumentation.getTargetContext().getPackageName(),
+                        Settings.TetherSettingsActivity.class.getName())
+                .setPackage(mInstrumentation.getTargetContext().getPackageName());
+    }
+
+    @After
+    public void tearDown() {
+        mDevice.pressHome();
+    }
+
+    @Test
+    public void launchTetherSettings_shouldHaveAllFields() {
+        launchWifiTetherActivity();
+        onView(withText("Network name")).check(matches(isDisplayed()));
+        onView(withText("Password")).check(matches(isDisplayed()));
+        onView(withText("Select AP Band")).check(matches(isDisplayed()));
+    }
+
+    private void launchWifiTetherActivity() {
+        mInstrumentation.startActivitySync(mTetherActivityIntent);
+        onView(withText("Portable Wi‑Fi hotspot")).perform();
+        UiObject2 item = mDevice.wait(Until.findObject(By.text("Portable Wi‑Fi hotspot")), TIMEOUT);
+        item.click();
+    }
+}