diff --git a/res/xml/tether_prefs.xml b/res/xml/tether_prefs.xml index a5062915280..709c4251161 100644 --- a/res/xml/tether_prefs.xml +++ b/res/xml/tether_prefs.xml @@ -17,10 +17,16 @@ + + + android:summary="@string/usb_tethering_subtext" /> + android:summary="@string/bluetooth_tethering_subtext" /> + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java index 708e17fe695..664916a0ff7 100644 --- a/src/com/android/settings/TetherSettings.java +++ b/src/com/android/settings/TetherSettings.java @@ -43,6 +43,8 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 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 @@ public class TetherSettings extends RestrictedSettingsFragment 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 @@ public class TetherSettings extends RestrictedSettingsFragment 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; @@ -126,6 +124,13 @@ public class TetherSettings extends RestrictedSettingsFragment super(UserManager.DISALLOW_CONFIG_TETHERING); } + @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 @@ public class TetherSettings extends RestrictedSettingsFragment 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 @@ public class TetherSettings extends RestrictedSettingsFragment 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 00000000000..53ff37a8d84 --- /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 5d725d8772d..675bf285e48 100644 --- a/src/com/android/settings/wifi/WifiApEnabler.java +++ b/src/com/android/settings/wifi/WifiApEnabler.java @@ -32,6 +32,10 @@ import com.android.settings.datausage.DataSaverBackend; 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 2a17dfcccc0..6f873426db0 100644 --- a/src/com/android/settings/wifi/WifiConfigController.java +++ b/src/com/android/settings/wifi/WifiConfigController.java @@ -110,7 +110,6 @@ public class WifiConfigController implements TextWatcher, 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 mPhase2PeapAdapter; @@ -463,7 +462,7 @@ public class WifiConfigController implements TextWatcher, 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 00000000000..7bd69dbfdd8 --- /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 00000000000..fc1719c6482 --- /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 00000000000..37da38ec3c7 --- /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 00000000000..eb211755445 --- /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 00000000000..a929b8612cd --- /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 00000000000..46fb7a983ab --- /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 active = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_ACTIVE_TETHER); + List 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 00000000000..a4c6c67f575 --- /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 00000000000..2584ed463e4 --- /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 getPreferenceControllers(Context context) { + final List 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 00000000000..e3c60980830 --- /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 b9e328c347a..d901a1173ea 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$AdminGrantedPermissionLo 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 23e1e2eba74..d05bee6c830 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 00000000000..88a51474187 --- /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 00000000000..1ccdb1f6eff --- /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 00000000000..a7e00ab544e --- /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 00000000000..7ea2ea92044 --- /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 00000000000..c3bc1eb01d7 --- /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 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 00000000000..f43e3a7b53f --- /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 f9c04894497..060c3e11bcf 100644 --- a/tests/unit/Android.mk +++ b/tests/unit/Android.mk @@ -9,10 +9,12 @@ LOCAL_JAVA_LIBRARIES := android.test.runner 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 00000000000..26a711b863e --- /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(); + } +}