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