| /* |
| * Copyright (C) 2020 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; |
| |
| import static android.net.ConnectivityManager.TETHERING_WIFI; |
| import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED; |
| import static android.net.wifi.WifiManager.WIFI_AP_STATE_CHANGED_ACTION; |
| |
| import android.app.settings.SettingsEnums; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothPan; |
| import android.bluetooth.BluetoothProfile; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.net.wifi.SoftApConfiguration; |
| import android.net.wifi.WifiManager; |
| import android.os.Bundle; |
| import android.os.UserManager; |
| import android.text.TextUtils; |
| import android.util.FeatureFlagUtils; |
| import android.util.Log; |
| |
| import androidx.annotation.VisibleForTesting; |
| import androidx.preference.Preference; |
| import androidx.preference.PreferenceGroup; |
| |
| import com.android.settings.core.FeatureFlags; |
| import com.android.settings.dashboard.RestrictedDashboardFragment; |
| import com.android.settings.datausage.DataSaverBackend; |
| import com.android.settings.network.BluetoothTetherPreferenceController; |
| import com.android.settings.network.EthernetTetherPreferenceController; |
| import com.android.settings.network.TetherEnabler; |
| import com.android.settings.network.UsbTetherPreferenceController; |
| import com.android.settings.network.WifiTetherDisablePreferenceController; |
| import com.android.settings.search.BaseSearchIndexProvider; |
| import com.android.settings.widget.MainSwitchBarController; |
| import com.android.settings.widget.SettingsMainSwitchBar; |
| import com.android.settings.wifi.tether.WifiTetherApBandPreferenceController; |
| import com.android.settings.wifi.tether.WifiTetherAutoOffPreferenceController; |
| import com.android.settings.wifi.tether.WifiTetherBasePreferenceController; |
| import com.android.settings.wifi.tether.WifiTetherFooterPreferenceController; |
| import com.android.settings.wifi.tether.WifiTetherPasswordPreferenceController; |
| import com.android.settings.wifi.tether.WifiTetherSSIDPreferenceController; |
| import com.android.settings.wifi.tether.WifiTetherSecurityPreferenceController; |
| import com.android.settingslib.TetherUtil; |
| import com.android.settingslib.core.AbstractPreferenceController; |
| import com.android.settingslib.search.SearchIndexable; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * Displays preferences for all Tethering options. |
| */ |
| @SearchIndexable |
| public class AllInOneTetherSettings extends RestrictedDashboardFragment |
| implements DataSaverBackend.Listener, |
| WifiTetherBasePreferenceController.OnTetherConfigUpdateListener { |
| |
| // TODO(b/148622133): Should clean up the postfix once this fragment replaced TetherSettings. |
| public static final String DEDUP_POSTFIX = "_2"; |
| |
| @VisibleForTesting |
| static final String KEY_WIFI_TETHER_NETWORK_NAME = "wifi_tether_network_name" + DEDUP_POSTFIX; |
| @VisibleForTesting |
| static final String KEY_WIFI_TETHER_NETWORK_PASSWORD = |
| "wifi_tether_network_password" + DEDUP_POSTFIX; |
| @VisibleForTesting |
| static final String KEY_WIFI_TETHER_AUTO_OFF = "wifi_tether_auto_turn_off" + DEDUP_POSTFIX; |
| @VisibleForTesting |
| static final String KEY_WIFI_TETHER_NETWORK_AP_BAND = |
| "wifi_tether_network_ap_band" + DEDUP_POSTFIX; |
| @VisibleForTesting |
| static final String KEY_WIFI_TETHER_SECURITY = "wifi_tether_security" + DEDUP_POSTFIX; |
| |
| private static final String KEY_DATA_SAVER_FOOTER = "disabled_on_data_saver" + DEDUP_POSTFIX; |
| private static final String KEY_WIFI_TETHER_GROUP = "wifi_tether_settings_group"; |
| public static final String WIFI_TETHER_DISABLE_KEY = "disable_wifi_tethering"; |
| public static final String USB_TETHER_KEY = "enable_usb_tethering"; |
| public static final String BLUETOOTH_TETHER_KEY = "enable_bluetooth_tethering" + DEDUP_POSTFIX; |
| public static final String ETHERNET_TETHER_KEY = "enable_ethernet_tethering" + DEDUP_POSTFIX; |
| |
| @VisibleForTesting |
| static final int EXPANDED_CHILD_COUNT_DEFAULT = 4; |
| @VisibleForTesting |
| static final int EXPANDED_CHILD_COUNT_WITH_SECURITY_NON = 3; |
| @VisibleForTesting |
| static final int EXPANDED_CHILD_COUNT_MAX = Integer.MAX_VALUE; |
| private static final String TAG = "AllInOneTetherSettings"; |
| |
| private boolean mUnavailable; |
| |
| private DataSaverBackend mDataSaverBackend; |
| private boolean mDataSaverEnabled; |
| private Preference mDataSaverFooter; |
| |
| private WifiManager mWifiManager; |
| private boolean mRestartWifiApAfterConfigChange; |
| private final AtomicReference<BluetoothPan> mBluetoothPan = new AtomicReference<>(); |
| |
| private WifiTetherSSIDPreferenceController mSSIDPreferenceController; |
| private WifiTetherPasswordPreferenceController mPasswordPreferenceController; |
| private WifiTetherApBandPreferenceController mApBandPreferenceController; |
| private WifiTetherSecurityPreferenceController mSecurityPreferenceController; |
| private PreferenceGroup mWifiTetherGroup; |
| private boolean mShouldShowWifiConfig = true; |
| private boolean mHasShownAdvance; |
| private TetherEnabler mTetherEnabler; |
| @VisibleForTesting |
| final TetherEnabler.OnTetherStateUpdateListener mStateUpdateListener = |
| state -> { |
| mShouldShowWifiConfig = TetherEnabler.isTethering(state, TETHERING_WIFI) |
| || state == TetherEnabler.TETHERING_OFF; |
| getPreferenceScreen().setInitialExpandedChildrenCount( |
| getInitialExpandedChildCount()); |
| mWifiTetherGroup.setVisible(mShouldShowWifiConfig); |
| }; |
| |
| private final BroadcastReceiver mTetherChangeReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context content, Intent intent) { |
| String action = intent.getAction(); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, |
| "updating display config due to receiving broadcast action " + action); |
| } |
| updateDisplayWithNewConfig(); |
| if (TextUtils.equals(action, ACTION_TETHER_STATE_CHANGED)) { |
| restartWifiTetherIfNeed(mWifiManager.getWifiApState()); |
| } else if (TextUtils.equals(action, WIFI_AP_STATE_CHANGED_ACTION)) { |
| restartWifiTetherIfNeed(intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE, 0)); |
| } |
| } |
| |
| private void restartWifiTetherIfNeed(int state) { |
| if (state == WifiManager.WIFI_AP_STATE_DISABLED |
| && mRestartWifiApAfterConfigChange) { |
| mRestartWifiApAfterConfigChange = false; |
| mTetherEnabler.startTethering(TETHERING_WIFI); |
| } |
| } |
| }; |
| |
| private final BluetoothProfile.ServiceListener mProfileServiceListener = |
| new BluetoothProfile.ServiceListener() { |
| public void onServiceConnected(int profile, BluetoothProfile proxy) { |
| mBluetoothPan.set((BluetoothPan) proxy); |
| } |
| |
| public void onServiceDisconnected(int profile) { |
| mBluetoothPan.set(null); |
| } |
| }; |
| |
| @Override |
| public int getMetricsCategory() { |
| return SettingsEnums.TETHER; |
| } |
| |
| public AllInOneTetherSettings() { |
| super(UserManager.DISALLOW_CONFIG_TETHERING); |
| } |
| |
| @Override |
| public void onAttach(Context context) { |
| super.onAttach(context); |
| mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); |
| |
| mSSIDPreferenceController = use(WifiTetherSSIDPreferenceController.class); |
| mSecurityPreferenceController = use(WifiTetherSecurityPreferenceController.class); |
| mPasswordPreferenceController = use(WifiTetherPasswordPreferenceController.class); |
| mApBandPreferenceController = use(WifiTetherApBandPreferenceController.class); |
| getSettingsLifecycle().addObserver(use(UsbTetherPreferenceController.class)); |
| getSettingsLifecycle().addObserver(use(BluetoothTetherPreferenceController.class)); |
| getSettingsLifecycle().addObserver(use(EthernetTetherPreferenceController.class)); |
| getSettingsLifecycle().addObserver(use(WifiTetherDisablePreferenceController.class)); |
| } |
| |
| @Override |
| public void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| mDataSaverBackend = new DataSaverBackend(getContext()); |
| mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled(); |
| mDataSaverFooter = findPreference(KEY_DATA_SAVER_FOOTER); |
| mWifiTetherGroup = findPreference(KEY_WIFI_TETHER_GROUP); |
| |
| setIfOnlyAvailableForAdmins(true); |
| if (isUiRestricted()) { |
| mUnavailable = true; |
| return; |
| } |
| |
| mDataSaverBackend.addListener(this); |
| |
| // Set initial state based on Data Saver mode. |
| onDataSaverChanged(mDataSaverBackend.isDataSaverEnabled()); |
| } |
| |
| @Override |
| public void onActivityCreated(Bundle savedInstanceState) { |
| super.onActivityCreated(savedInstanceState); |
| if (mUnavailable) { |
| return; |
| } |
| // Assume we are in a SettingsActivity. This is only safe because we currently use |
| // SettingsActivity as base for all preference fragments. |
| final SettingsActivity activity = (SettingsActivity) getActivity(); |
| final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); |
| if (adapter != null) { |
| adapter.getProfileProxy(activity.getApplicationContext(), mProfileServiceListener, |
| BluetoothProfile.PAN); |
| } |
| final SettingsMainSwitchBar mainSwitch = activity.getSwitchBar(); |
| mTetherEnabler = new TetherEnabler(activity, |
| new MainSwitchBarController(mainSwitch), mBluetoothPan); |
| getSettingsLifecycle().addObserver(mTetherEnabler); |
| use(UsbTetherPreferenceController.class).setTetherEnabler(mTetherEnabler); |
| use(BluetoothTetherPreferenceController.class).setTetherEnabler(mTetherEnabler); |
| use(EthernetTetherPreferenceController.class).setTetherEnabler(mTetherEnabler); |
| use(WifiTetherDisablePreferenceController.class).setTetherEnabler(mTetherEnabler); |
| mainSwitch.show(); |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| |
| if (mUnavailable) { |
| if (!isUiRestrictedByOnlyAdmin()) { |
| getEmptyTextView().setText(R.string.tethering_settings_not_available); |
| } |
| getPreferenceScreen().removeAll(); |
| return; |
| } |
| final Context context = getContext(); |
| if (context != null) { |
| // The intent WIFI_AP_STATE_CHANGED_ACTION is not sticky intent after SC-V2 |
| // But ACTION_TETHER_STATE_CHANGED is still sticky intent. So no need to handle |
| // initial state for WIFI_AP_STATE_CHANGED_ACTION |
| IntentFilter filter = new IntentFilter(ACTION_TETHER_STATE_CHANGED); |
| filter.addAction(WIFI_AP_STATE_CHANGED_ACTION); |
| context.registerReceiver(mTetherChangeReceiver, filter); |
| } |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| if (mUnavailable) { |
| return; |
| } |
| if (mTetherEnabler != null) { |
| mTetherEnabler.addListener(mStateUpdateListener); |
| } |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| if (mUnavailable) { |
| return; |
| } |
| if (mTetherEnabler != null) { |
| mTetherEnabler.removeListener(mStateUpdateListener); |
| } |
| } |
| |
| @Override |
| public void onStop() { |
| super.onStop(); |
| if (mUnavailable) { |
| return; |
| } |
| final Context context = getContext(); |
| if (context != null) { |
| context.unregisterReceiver(mTetherChangeReceiver); |
| } |
| } |
| |
| @Override |
| public void onDestroy() { |
| mDataSaverBackend.remListener(this); |
| super.onDestroy(); |
| } |
| |
| @Override |
| public void onDataSaverChanged(boolean isDataSaving) { |
| mDataSaverEnabled = isDataSaving; |
| mDataSaverFooter.setVisible(mDataSaverEnabled); |
| } |
| |
| @Override |
| public void onAllowlistStatusChanged(int uid, boolean isAllowlisted) { |
| // Do nothing |
| } |
| |
| @Override |
| public void onDenylistStatusChanged(int uid, boolean isDenylisted) { |
| // Do nothing |
| } |
| |
| @Override |
| protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { |
| return buildPreferenceControllers(context, this); |
| } |
| |
| private static List<AbstractPreferenceController> buildPreferenceControllers(Context context, |
| WifiTetherBasePreferenceController.OnTetherConfigUpdateListener listener) { |
| final List<AbstractPreferenceController> controllers = new ArrayList<>(); |
| controllers.add( |
| new WifiTetherSSIDPreferenceController(context, listener)); |
| controllers.add( |
| new WifiTetherPasswordPreferenceController(context, listener)); |
| controllers.add( |
| new WifiTetherApBandPreferenceController(context, listener)); |
| controllers.add( |
| new WifiTetherSecurityPreferenceController(context, listener)); |
| controllers.add( |
| new WifiTetherAutoOffPreferenceController(context, KEY_WIFI_TETHER_AUTO_OFF)); |
| controllers.add( |
| new WifiTetherFooterPreferenceController(context)); |
| |
| return controllers; |
| } |
| |
| @Override |
| protected int getPreferenceScreenResId() { |
| return R.xml.all_tether_prefs; |
| } |
| |
| @Override |
| protected String getLogTag() { |
| return TAG; |
| } |
| |
| @Override |
| public int getHelpResource() { |
| return R.string.help_url_tether; |
| } |
| |
| @Override |
| public void onTetherConfigUpdated(AbstractPreferenceController controller) { |
| final SoftApConfiguration config = buildNewConfig(); |
| mPasswordPreferenceController.setSecurityType(config.getSecurityType()); |
| mWifiManager.setSoftApConfiguration(config); |
| |
| if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "Wifi AP config changed while enabled, stop and restart"); |
| } |
| mRestartWifiApAfterConfigChange = true; |
| mTetherEnabler.stopTethering(TETHERING_WIFI); |
| } |
| } |
| |
| private SoftApConfiguration buildNewConfig() { |
| final SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(); |
| final int securityType = mSecurityPreferenceController.getSecurityType(); |
| configBuilder.setSsid(mSSIDPreferenceController.getSSID()); |
| if (securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) { |
| configBuilder.setPassphrase( |
| mPasswordPreferenceController.getPasswordValidated(securityType), |
| SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); |
| } |
| configBuilder.setBand(mApBandPreferenceController.getBandIndex()); |
| return configBuilder.build(); |
| } |
| |
| private void updateDisplayWithNewConfig() { |
| mSSIDPreferenceController.updateDisplay(); |
| mSecurityPreferenceController.updateDisplay(); |
| mPasswordPreferenceController.updateDisplay(); |
| mApBandPreferenceController.updateDisplay(); |
| } |
| |
| @Override |
| public int getInitialExpandedChildCount() { |
| if (mHasShownAdvance || !mShouldShowWifiConfig) { |
| mHasShownAdvance = true; |
| return EXPANDED_CHILD_COUNT_MAX; |
| } |
| |
| if (mSecurityPreferenceController == null) { |
| return EXPANDED_CHILD_COUNT_DEFAULT; |
| } |
| |
| return (mSecurityPreferenceController.getSecurityType() |
| == SoftApConfiguration.SECURITY_TYPE_OPEN) |
| ? EXPANDED_CHILD_COUNT_WITH_SECURITY_NON : EXPANDED_CHILD_COUNT_DEFAULT; |
| } |
| |
| @Override |
| public void onExpandButtonClick() { |
| super.onExpandButtonClick(); |
| mHasShownAdvance = true; |
| } |
| |
| public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = |
| new BaseSearchIndexProvider(R.xml.all_tether_prefs) { |
| |
| @Override |
| public List<String> getNonIndexableKeys(Context context) { |
| final List<String> keys = super.getNonIndexableKeys(context); |
| |
| if (!TetherUtil.isTetherAvailable(context)) { |
| keys.add(KEY_WIFI_TETHER_NETWORK_NAME); |
| keys.add(KEY_WIFI_TETHER_NETWORK_PASSWORD); |
| keys.add(KEY_WIFI_TETHER_AUTO_OFF); |
| keys.add(KEY_WIFI_TETHER_NETWORK_AP_BAND); |
| keys.add(KEY_WIFI_TETHER_SECURITY); |
| } |
| return keys; |
| } |
| |
| @Override |
| protected boolean isPageSearchEnabled(Context context) { |
| return FeatureFlagUtils.isEnabled(context, FeatureFlags.TETHER_ALL_IN_ONE); |
| } |
| |
| @Override |
| public List<AbstractPreferenceController> createPreferenceControllers( |
| Context context) { |
| return buildPreferenceControllers(context, null /*listener*/); |
| } |
| }; |
| } |