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.TetheringManager; 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 import android.util.FeatureFlagUtils; 37 38 import androidx.annotation.VisibleForTesting; 39 import androidx.preference.Preference; 40 import androidx.preference.PreferenceScreen; 41 42 import com.android.settings.R; 43 import com.android.settings.core.FeatureFlags; 44 import com.android.settings.core.PreferenceControllerMixin; 45 import com.android.settingslib.TetherUtil; 46 import com.android.settingslib.core.AbstractPreferenceController; 47 import com.android.settingslib.core.lifecycle.Lifecycle; 48 import com.android.settingslib.core.lifecycle.LifecycleObserver; 49 import com.android.settingslib.core.lifecycle.events.OnCreate; 50 import com.android.settingslib.core.lifecycle.events.OnDestroy; 51 import com.android.settingslib.core.lifecycle.events.OnPause; 52 import com.android.settingslib.core.lifecycle.events.OnResume; 53 54 import java.util.concurrent.atomic.AtomicReference; 55 56 public class TetherPreferenceController extends AbstractPreferenceController implements 57 PreferenceControllerMixin, LifecycleObserver, OnCreate, OnResume, OnPause, OnDestroy { 58 59 private static final String KEY_TETHER_SETTINGS = "tether_settings"; 60 61 private final boolean mAdminDisallowedTetherConfig; 62 private final AtomicReference<BluetoothPan> mBluetoothPan; 63 private final BluetoothAdapter mBluetoothAdapter; 64 private final TetheringManager mTetheringManager; 65 @VisibleForTesting 66 final BluetoothProfile.ServiceListener mBtProfileServiceListener = 67 new android.bluetooth.BluetoothProfile.ServiceListener() { 68 public void onServiceConnected(int profile, BluetoothProfile proxy) { 69 mBluetoothPan.set((BluetoothPan) proxy); 70 updateSummary(); 71 } 72 73 public void onServiceDisconnected(int profile) { 74 mBluetoothPan.set(null); 75 } 76 }; 77 78 private SettingObserver mAirplaneModeObserver; 79 private Preference mPreference; 80 private TetherBroadcastReceiver mTetherReceiver; 81 82 @VisibleForTesting(otherwise = VisibleForTesting.NONE) TetherPreferenceController()83 TetherPreferenceController() { 84 super(null); 85 mAdminDisallowedTetherConfig = false; 86 mBluetoothPan = new AtomicReference<>(); 87 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 88 mTetheringManager = null; 89 } 90 TetherPreferenceController(Context context, Lifecycle lifecycle)91 public TetherPreferenceController(Context context, Lifecycle lifecycle) { 92 super(context); 93 mBluetoothPan = new AtomicReference<>(); 94 mAdminDisallowedTetherConfig = isTetherConfigDisallowed(context); 95 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 96 mTetheringManager = context.getSystemService(TetheringManager.class); 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(mTetheringManager)); 109 } 110 } 111 112 @Override isAvailable()113 public boolean isAvailable() { 114 return TetherUtil.isTetherAvailable(mContext) 115 && !FeatureFlagUtils.isEnabled(mContext, FeatureFlags.TETHER_ALL_IN_ONE); 116 } 117 118 @Override updateState(Preference preference)119 public void updateState(Preference preference) { 120 updateSummary(); 121 } 122 123 @Override getPreferenceKey()124 public String getPreferenceKey() { 125 return KEY_TETHER_SETTINGS; 126 } 127 128 @Override onCreate(Bundle savedInstanceState)129 public void onCreate(Bundle savedInstanceState) { 130 if (mBluetoothAdapter != null && 131 mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { 132 mBluetoothAdapter.getProfileProxy(mContext, mBtProfileServiceListener, 133 BluetoothProfile.PAN); 134 } 135 } 136 137 @Override onResume()138 public void onResume() { 139 if (mAirplaneModeObserver == null) { 140 mAirplaneModeObserver = new SettingObserver(); 141 } 142 if (mTetherReceiver == null) { 143 mTetherReceiver = new TetherBroadcastReceiver(); 144 } 145 mContext.registerReceiver( 146 mTetherReceiver, new IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED)); 147 mContext.getContentResolver() 148 .registerContentObserver(mAirplaneModeObserver.uri, false, mAirplaneModeObserver); 149 } 150 151 @Override onPause()152 public void onPause() { 153 if (mAirplaneModeObserver != null) { 154 mContext.getContentResolver().unregisterContentObserver(mAirplaneModeObserver); 155 } 156 if (mTetherReceiver != null) { 157 mContext.unregisterReceiver(mTetherReceiver); 158 } 159 } 160 161 @Override onDestroy()162 public void onDestroy() { 163 final BluetoothProfile profile = mBluetoothPan.getAndSet(null); 164 if (profile != null && mBluetoothAdapter != null) { 165 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, profile); 166 } 167 } 168 isTetherConfigDisallowed(Context context)169 public static boolean isTetherConfigDisallowed(Context context) { 170 return checkIfRestrictionEnforced( 171 context, DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()) != null; 172 } 173 174 @VisibleForTesting updateSummary()175 void updateSummary() { 176 if (mPreference == null) { 177 // Preference is not ready yet. 178 return; 179 } 180 String[] allTethered = mTetheringManager.getTetheredIfaces(); 181 String[] wifiTetherRegex = mTetheringManager.getTetherableWifiRegexs(); 182 String[] bluetoothRegex = mTetheringManager.getTetherableBluetoothRegexs(); 183 184 boolean hotSpotOn = false; 185 boolean tetherOn = false; 186 if (allTethered != null) { 187 if (wifiTetherRegex != null) { 188 for (String tethered : allTethered) { 189 for (String regex : wifiTetherRegex) { 190 if (tethered.matches(regex)) { 191 hotSpotOn = true; 192 break; 193 } 194 } 195 } 196 } 197 if (allTethered.length > 1) { 198 // We have more than 1 tethered connection 199 tetherOn = true; 200 } else if (allTethered.length == 1) { 201 // We have more than 1 tethered, it's either wifiTether (hotspot), or other type of 202 // tether. 203 tetherOn = !hotSpotOn; 204 } else { 205 // No tethered connection. 206 tetherOn = false; 207 } 208 } 209 if (!tetherOn 210 && bluetoothRegex != null && bluetoothRegex.length > 0 211 && mBluetoothAdapter != null 212 && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { 213 // Check bluetooth state. It's not included in mTetheringManager.getTetheredIfaces. 214 final BluetoothPan pan = mBluetoothPan.get(); 215 tetherOn = pan != null && pan.isTetheringOn(); 216 } 217 if (!hotSpotOn && !tetherOn) { 218 // Both off 219 mPreference.setSummary(R.string.switch_off_text); 220 } else if (hotSpotOn && tetherOn) { 221 // Both on 222 mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_on); 223 } else if (hotSpotOn) { 224 mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_off); 225 } else { 226 mPreference.setSummary(R.string.tether_settings_summary_hotspot_off_tether_on); 227 } 228 } 229 updateSummaryToOff()230 private void updateSummaryToOff() { 231 if (mPreference == null) { 232 // Preference is not ready yet. 233 return; 234 } 235 mPreference.setSummary(R.string.switch_off_text); 236 } 237 238 class SettingObserver extends ContentObserver { 239 240 public final Uri uri; 241 SettingObserver()242 public SettingObserver() { 243 super(new Handler()); 244 uri = Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON); 245 } 246 247 @Override onChange(boolean selfChange, Uri uri)248 public void onChange(boolean selfChange, Uri uri) { 249 super.onChange(selfChange, uri); 250 if (this.uri.equals(uri)) { 251 boolean isAirplaneMode = Settings.Global.getInt(mContext.getContentResolver(), 252 Settings.Global.AIRPLANE_MODE_ON, 0) != 0; 253 if (isAirplaneMode) { 254 // Airplane mode is on. Update summary to say tether is OFF directly. We cannot 255 // go through updateSummary() because turning off tether takes time, and we 256 // might still get "ON" status when rerun updateSummary(). So, just say it's off 257 updateSummaryToOff(); 258 } 259 } 260 } 261 } 262 263 @VisibleForTesting 264 class TetherBroadcastReceiver extends BroadcastReceiver { 265 266 @Override onReceive(Context context, Intent intent)267 public void onReceive(Context context, Intent intent) { 268 updateSummary(); 269 } 270 271 } 272 } 273