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