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) 80 TetherPreferenceController() { 81 super(null); 82 mAdminDisallowedTetherConfig = false; 83 mBluetoothPan = new AtomicReference<>(); 84 mConnectivityManager = null; 85 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 86 } 87 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 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 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 123 public void updateState(Preference preference) { 124 updateSummary(); 125 } 126 127 @Override 128 public String getPreferenceKey() { 129 return KEY_TETHER_SETTINGS; 130 } 131 132 @Override 133 public void onCreate(Bundle savedInstanceState) { 134 if (mBluetoothAdapter != null) { 135 mBluetoothAdapter.getProfileProxy(mContext, mBtProfileServiceListener, 136 BluetoothProfile.PAN); 137 } 138 } 139 140 @Override 141 public void onResume() { 142 if (mAirplaneModeObserver == null) { 143 mAirplaneModeObserver = new SettingObserver(); 144 } 145 if (mTetherReceiver == null) { 146 mTetherReceiver = new TetherBroadcastReceiver(); 147 } 148 mContext.registerReceiver( 149 mTetherReceiver, new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); 150 mContext.getContentResolver() 151 .registerContentObserver(mAirplaneModeObserver.uri, false, mAirplaneModeObserver); 152 } 153 154 @Override 155 public void onPause() { 156 if (mAirplaneModeObserver != null) { 157 mContext.getContentResolver().unregisterContentObserver(mAirplaneModeObserver); 158 } 159 if (mTetherReceiver != null) { 160 mContext.unregisterReceiver(mTetherReceiver); 161 } 162 } 163 164 @Override 165 public void onDestroy() { 166 final BluetoothProfile profile = mBluetoothPan.getAndSet(null); 167 if (profile != null && mBluetoothAdapter != null) { 168 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, profile); 169 } 170 } 171 172 public static boolean isTetherConfigDisallowed(Context context) { 173 return checkIfRestrictionEnforced( 174 context, DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()) != null; 175 } 176 177 @VisibleForTesting 178 void updateSummary() { 179 if (mPreference == null) { 180 // Preference is not ready yet. 181 return; 182 } 183 String[] allTethered = mConnectivityManager.getTetheredIfaces(); 184 String[] wifiTetherRegex = mConnectivityManager.getTetherableWifiRegexs(); 185 String[] bluetoothRegex = mConnectivityManager.getTetherableBluetoothRegexs(); 186 187 boolean hotSpotOn = false; 188 boolean tetherOn = false; 189 if (allTethered != null) { 190 if (wifiTetherRegex != null) { 191 for (String tethered : allTethered) { 192 for (String regex : wifiTetherRegex) { 193 if (tethered.matches(regex)) { 194 hotSpotOn = true; 195 break; 196 } 197 } 198 } 199 } 200 if (allTethered.length > 1) { 201 // We have more than 1 tethered connection 202 tetherOn = true; 203 } else if (allTethered.length == 1) { 204 // We have more than 1 tethered, it's either wifiTether (hotspot), or other type of 205 // tether. 206 tetherOn = !hotSpotOn; 207 } else { 208 // No tethered connection. 209 tetherOn = false; 210 } 211 } 212 if (!tetherOn 213 && bluetoothRegex != null && bluetoothRegex.length > 0 214 && mBluetoothAdapter != null 215 && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { 216 // Check bluetooth state. It's not included in mConnectivityManager.getTetheredIfaces. 217 final BluetoothPan pan = mBluetoothPan.get(); 218 tetherOn = pan != null && pan.isTetheringOn(); 219 } 220 if (!hotSpotOn && !tetherOn) { 221 // Both off 222 mPreference.setSummary(R.string.switch_off_text); 223 } else if (hotSpotOn && tetherOn) { 224 // Both on 225 mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_on); 226 } else if (hotSpotOn) { 227 mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_off); 228 } else { 229 mPreference.setSummary(R.string.tether_settings_summary_hotspot_off_tether_on); 230 } 231 } 232 233 private void updateSummaryToOff() { 234 if (mPreference == null) { 235 // Preference is not ready yet. 236 return; 237 } 238 mPreference.setSummary(R.string.switch_off_text); 239 } 240 241 class SettingObserver extends ContentObserver { 242 243 public final Uri uri; 244 245 public SettingObserver() { 246 super(new Handler()); 247 uri = Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON); 248 } 249 250 @Override 251 public void onChange(boolean selfChange, Uri uri) { 252 super.onChange(selfChange, uri); 253 if (this.uri.equals(uri)) { 254 boolean isAirplaneMode = Settings.Global.getInt(mContext.getContentResolver(), 255 Settings.Global.AIRPLANE_MODE_ON, 0) != 0; 256 if (isAirplaneMode) { 257 // Airplane mode is on. Update summary to say tether is OFF directly. We cannot 258 // go through updateSummary() because turning off tether takes time, and we 259 // might still get "ON" status when rerun updateSummary(). So, just say it's off 260 updateSummaryToOff(); 261 } 262 } 263 } 264 } 265 266 @VisibleForTesting 267 class TetherBroadcastReceiver extends BroadcastReceiver { 268 269 @Override 270 public void onReceive(Context context, Intent intent) { 271 updateSummary(); 272 } 273 274 } 275 } 276