1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.settings.network; 17 18 import static android.os.UserManager.DISALLOW_CONFIG_TETHERING; 19 20 import static com.android.settingslib.RestrictedLockUtilsInternal.checkIfRestrictionEnforced; 21 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothPan; 24 import android.bluetooth.BluetoothProfile; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.database.ContentObserver; 30 import android.net.ConnectivityManager; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 37 import androidx.annotation.VisibleForTesting; 38 import androidx.preference.Preference; 39 import androidx.preference.PreferenceScreen; 40 41 import com.android.settings.R; 42 import com.android.settings.TetherSettings; 43 import com.android.settings.core.PreferenceControllerMixin; 44 import com.android.settingslib.TetherUtil; 45 import com.android.settingslib.core.AbstractPreferenceController; 46 import com.android.settingslib.core.lifecycle.Lifecycle; 47 import com.android.settingslib.core.lifecycle.LifecycleObserver; 48 import com.android.settingslib.core.lifecycle.events.OnCreate; 49 import com.android.settingslib.core.lifecycle.events.OnDestroy; 50 import com.android.settingslib.core.lifecycle.events.OnPause; 51 import com.android.settingslib.core.lifecycle.events.OnResume; 52 53 import java.util.concurrent.atomic.AtomicReference; 54 55 public class TetherPreferenceController extends AbstractPreferenceController implements 56 PreferenceControllerMixin, LifecycleObserver, OnCreate, OnResume, OnPause, OnDestroy { 57 58 private static final String KEY_TETHER_SETTINGS = "tether_settings"; 59 60 private final boolean mAdminDisallowedTetherConfig; 61 private final AtomicReference<BluetoothPan> mBluetoothPan; 62 private final ConnectivityManager mConnectivityManager; 63 private final BluetoothAdapter mBluetoothAdapter; 64 @VisibleForTesting 65 final BluetoothProfile.ServiceListener mBtProfileServiceListener = 66 new android.bluetooth.BluetoothProfile.ServiceListener() { 67 public void onServiceConnected(int profile, BluetoothProfile proxy) { 68 mBluetoothPan.set((BluetoothPan) proxy); 69 updateSummary(); 70 } 71 72 public void onServiceDisconnected(int profile) { 73 mBluetoothPan.set(null); 74 } 75 }; 76 77 private SettingObserver mAirplaneModeObserver; 78 private Preference mPreference; 79 private TetherBroadcastReceiver mTetherReceiver; 80 81 @VisibleForTesting(otherwise = VisibleForTesting.NONE) TetherPreferenceController()82 TetherPreferenceController() { 83 super(null); 84 mAdminDisallowedTetherConfig = false; 85 mBluetoothPan = new AtomicReference<>(); 86 mConnectivityManager = null; 87 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 88 } 89 TetherPreferenceController(Context context, Lifecycle lifecycle)90 public TetherPreferenceController(Context context, Lifecycle lifecycle) { 91 super(context); 92 mBluetoothPan = new AtomicReference<>(); 93 mAdminDisallowedTetherConfig = isTetherConfigDisallowed(context); 94 mConnectivityManager = 95 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 96 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 97 if (lifecycle != null) { 98 lifecycle.addObserver(this); 99 } 100 } 101 102 @Override displayPreference(PreferenceScreen screen)103 public void displayPreference(PreferenceScreen screen) { 104 super.displayPreference(screen); 105 mPreference = screen.findPreference(KEY_TETHER_SETTINGS); 106 if (mPreference != null && !mAdminDisallowedTetherConfig) { 107 mPreference.setTitle( 108 com.android.settingslib.Utils.getTetheringLabel(mConnectivityManager)); 109 110 // Grey out if provisioning is not available. 111 mPreference.setEnabled(!TetherSettings.isProvisioningNeededButUnavailable(mContext)); 112 } 113 } 114 115 @Override isAvailable()116 public boolean isAvailable() { 117 return TetherUtil.isTetherAvailable(mContext); 118 } 119 120 @Override updateState(Preference preference)121 public void updateState(Preference preference) { 122 updateSummary(); 123 } 124 125 @Override getPreferenceKey()126 public String getPreferenceKey() { 127 return KEY_TETHER_SETTINGS; 128 } 129 130 @Override onCreate(Bundle savedInstanceState)131 public void onCreate(Bundle savedInstanceState) { 132 if (mBluetoothAdapter != null && 133 mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { 134 mBluetoothAdapter.getProfileProxy(mContext, mBtProfileServiceListener, 135 BluetoothProfile.PAN); 136 } 137 } 138 139 @Override onResume()140 public void onResume() { 141 if (mAirplaneModeObserver == null) { 142 mAirplaneModeObserver = new SettingObserver(); 143 } 144 if (mTetherReceiver == null) { 145 mTetherReceiver = new TetherBroadcastReceiver(); 146 } 147 mContext.registerReceiver( 148 mTetherReceiver, new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); 149 mContext.getContentResolver() 150 .registerContentObserver(mAirplaneModeObserver.uri, false, mAirplaneModeObserver); 151 } 152 153 @Override onPause()154 public void onPause() { 155 if (mAirplaneModeObserver != null) { 156 mContext.getContentResolver().unregisterContentObserver(mAirplaneModeObserver); 157 } 158 if (mTetherReceiver != null) { 159 mContext.unregisterReceiver(mTetherReceiver); 160 } 161 } 162 163 @Override onDestroy()164 public void onDestroy() { 165 final BluetoothProfile profile = mBluetoothPan.getAndSet(null); 166 if (profile != null && mBluetoothAdapter != null) { 167 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, profile); 168 } 169 } 170 isTetherConfigDisallowed(Context context)171 public static boolean isTetherConfigDisallowed(Context context) { 172 return checkIfRestrictionEnforced( 173 context, DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()) != null; 174 } 175 176 @VisibleForTesting updateSummary()177 void updateSummary() { 178 if (mPreference == null) { 179 // Preference is not ready yet. 180 return; 181 } 182 String[] allTethered = mConnectivityManager.getTetheredIfaces(); 183 String[] wifiTetherRegex = mConnectivityManager.getTetherableWifiRegexs(); 184 String[] bluetoothRegex = mConnectivityManager.getTetherableBluetoothRegexs(); 185 186 boolean hotSpotOn = false; 187 boolean tetherOn = false; 188 if (allTethered != null) { 189 if (wifiTetherRegex != null) { 190 for (String tethered : allTethered) { 191 for (String regex : wifiTetherRegex) { 192 if (tethered.matches(regex)) { 193 hotSpotOn = true; 194 break; 195 } 196 } 197 } 198 } 199 if (allTethered.length > 1) { 200 // We have more than 1 tethered connection 201 tetherOn = true; 202 } else if (allTethered.length == 1) { 203 // We have more than 1 tethered, it's either wifiTether (hotspot), or other type of 204 // tether. 205 tetherOn = !hotSpotOn; 206 } else { 207 // No tethered connection. 208 tetherOn = false; 209 } 210 } 211 if (!tetherOn 212 && bluetoothRegex != null && bluetoothRegex.length > 0 213 && mBluetoothAdapter != null 214 && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { 215 // Check bluetooth state. It's not included in mConnectivityManager.getTetheredIfaces. 216 final BluetoothPan pan = mBluetoothPan.get(); 217 tetherOn = pan != null && pan.isTetheringOn(); 218 } 219 if (!hotSpotOn && !tetherOn) { 220 // Both off 221 mPreference.setSummary(R.string.switch_off_text); 222 } else if (hotSpotOn && tetherOn) { 223 // Both on 224 mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_on); 225 } else if (hotSpotOn) { 226 mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_off); 227 } else { 228 mPreference.setSummary(R.string.tether_settings_summary_hotspot_off_tether_on); 229 } 230 } 231 updateSummaryToOff()232 private void updateSummaryToOff() { 233 if (mPreference == null) { 234 // Preference is not ready yet. 235 return; 236 } 237 mPreference.setSummary(R.string.switch_off_text); 238 } 239 240 class SettingObserver extends ContentObserver { 241 242 public final Uri uri; 243 SettingObserver()244 public SettingObserver() { 245 super(new Handler()); 246 uri = Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON); 247 } 248 249 @Override onChange(boolean selfChange, Uri uri)250 public void onChange(boolean selfChange, Uri uri) { 251 super.onChange(selfChange, uri); 252 if (this.uri.equals(uri)) { 253 boolean isAirplaneMode = Settings.Global.getInt(mContext.getContentResolver(), 254 Settings.Global.AIRPLANE_MODE_ON, 0) != 0; 255 if (isAirplaneMode) { 256 // Airplane mode is on. Update summary to say tether is OFF directly. We cannot 257 // go through updateSummary() because turning off tether takes time, and we 258 // might still get "ON" status when rerun updateSummary(). So, just say it's off 259 updateSummaryToOff(); 260 } 261 } 262 } 263 } 264 265 @VisibleForTesting 266 class TetherBroadcastReceiver extends BroadcastReceiver { 267 268 @Override onReceive(Context context, Intent intent)269 public void onReceive(Context context, Intent intent) { 270 updateSummary(); 271 } 272 273 } 274 } 275