/* * 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 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) { 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 createPreferenceControllers(Context context) { return buildPreferenceControllers(context, this); } private static List buildPreferenceControllers(Context context, WifiTetherBasePreferenceController.OnTetherConfigUpdateListener listener) { final List 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 getNonIndexableKeys(Context context) { final List 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 createPreferenceControllers( Context context) { return buildPreferenceControllers(context, null /*listener*/); } }; }