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/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);
+ }
+ }
+}