1 /* 2 * Copyright (C) 2010 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 17 package com.android.settings.bluetooth; 18 19 import static com.android.settings.network.SatelliteWarningDialogActivity.EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG; 20 import static com.android.settings.network.SatelliteWarningDialogActivity.TYPE_IS_BLUETOOTH; 21 22 import android.bluetooth.BluetoothAdapter; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.os.UserManager; 28 import android.provider.Settings; 29 import android.util.Log; 30 import android.widget.Toast; 31 32 import androidx.annotation.VisibleForTesting; 33 34 import com.android.settings.R; 35 import com.android.settings.network.SatelliteRepository; 36 import com.android.settings.network.SatelliteWarningDialogActivity; 37 import com.android.settings.widget.SwitchWidgetController; 38 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 39 import com.android.settingslib.WirelessUtils; 40 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 41 42 import java.util.concurrent.ExecutionException; 43 import java.util.concurrent.Executors; 44 import java.util.concurrent.TimeUnit; 45 import java.util.concurrent.TimeoutException; 46 import java.util.concurrent.atomic.AtomicBoolean; 47 48 /** 49 * BluetoothEnabler is a helper to manage the Bluetooth on/off checkbox 50 * preference. It turns on/off Bluetooth and ensures the summary of the 51 * preference reflects the current state. 52 */ 53 public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchChangeListener { 54 private static final String TAG = BluetoothEnabler.class.getSimpleName(); 55 private final SwitchWidgetController mSwitchController; 56 private final MetricsFeatureProvider mMetricsFeatureProvider; 57 private Context mContext; 58 private boolean mValidListener; 59 private final BluetoothAdapter mBluetoothAdapter; 60 private final IntentFilter mIntentFilter; 61 private final RestrictionUtils mRestrictionUtils; 62 private SwitchWidgetController.OnSwitchChangeListener mCallback; 63 64 private static final String EVENT_DATA_IS_BT_ON = "is_bluetooth_on"; 65 private static final int EVENT_UPDATE_INDEX = 0; 66 private final int mMetricsEvent; 67 private SatelliteRepository mSatelliteRepository; 68 @VisibleForTesting 69 AtomicBoolean mIsSatelliteOn = new AtomicBoolean(false); 70 71 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 72 @Override 73 public void onReceive(Context context, Intent intent) { 74 // Broadcast receiver is always running on the UI thread here, 75 // so we don't need consider thread synchronization. 76 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 77 handleStateChanged(state); 78 } 79 }; 80 BluetoothEnabler(Context context, SwitchWidgetController switchController, MetricsFeatureProvider metricsFeatureProvider, int metricsEvent, RestrictionUtils restrictionUtils)81 public BluetoothEnabler(Context context, SwitchWidgetController switchController, 82 MetricsFeatureProvider metricsFeatureProvider, int metricsEvent, 83 RestrictionUtils restrictionUtils) { 84 mContext = context; 85 mMetricsFeatureProvider = metricsFeatureProvider; 86 mSwitchController = switchController; 87 mSwitchController.setListener(this); 88 mSwitchController.setTitle(context.getString(R.string.bluetooth_main_switch_title)); 89 90 mValidListener = false; 91 mMetricsEvent = metricsEvent; 92 93 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 94 if (mBluetoothAdapter == null) { 95 // Bluetooth is not supported 96 mSwitchController.setEnabled(false); 97 } 98 mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 99 mRestrictionUtils = restrictionUtils; 100 mSatelliteRepository = new SatelliteRepository(context); 101 } 102 setupSwitchController()103 public void setupSwitchController() { 104 mSwitchController.setupView(); 105 } 106 teardownSwitchController()107 public void teardownSwitchController() { 108 mSwitchController.teardownView(); 109 } 110 resume(Context context)111 public void resume(Context context) { 112 if (mContext != context) { 113 mContext = context; 114 } 115 116 final boolean restricted = maybeEnforceRestrictions(); 117 118 if (mBluetoothAdapter == null) { 119 mSwitchController.setEnabled(false); 120 return; 121 } 122 123 // Bluetooth state is not sticky, so set it manually 124 if (!restricted) { 125 handleStateChanged(mBluetoothAdapter.getState()); 126 } 127 128 mSwitchController.startListening(); 129 mContext.registerReceiver(mReceiver, mIntentFilter, 130 Context.RECEIVER_EXPORTED_UNAUDITED); 131 mValidListener = true; 132 133 new Thread(() -> { 134 try { 135 mIsSatelliteOn.set(mSatelliteRepository.requestIsSessionStarted( 136 Executors.newSingleThreadExecutor()).get(3000, TimeUnit.MILLISECONDS)); 137 } catch (InterruptedException | ExecutionException | TimeoutException e) { 138 Log.e(TAG, "Error to get satellite status : " + e); 139 } 140 }).start(); 141 } 142 pause()143 public void pause() { 144 if (mBluetoothAdapter == null) { 145 return; 146 } 147 if (mValidListener) { 148 mSwitchController.stopListening(); 149 mContext.unregisterReceiver(mReceiver); 150 mValidListener = false; 151 } 152 } 153 handleStateChanged(int state)154 void handleStateChanged(int state) { 155 switch (state) { 156 case BluetoothAdapter.STATE_TURNING_ON: 157 mSwitchController.setEnabled(false); 158 break; 159 case BluetoothAdapter.STATE_ON: 160 setChecked(true); 161 mSwitchController.setEnabled(true); 162 break; 163 case BluetoothAdapter.STATE_TURNING_OFF: 164 mSwitchController.setEnabled(false); 165 break; 166 case BluetoothAdapter.STATE_OFF: 167 setChecked(false); 168 mSwitchController.setEnabled(true); 169 break; 170 default: 171 setChecked(false); 172 mSwitchController.setEnabled(true); 173 } 174 } 175 setChecked(boolean isChecked)176 private void setChecked(boolean isChecked) { 177 if (isChecked != mSwitchController.isChecked()) { 178 // set listener to null, so onCheckedChanged won't be called 179 // if the checked status on Switch isn't changed by user click 180 if (mValidListener) { 181 mSwitchController.stopListening(); 182 } 183 mSwitchController.setChecked(isChecked); 184 if (mValidListener) { 185 mSwitchController.startListening(); 186 } 187 } 188 } 189 190 @Override onSwitchToggled(boolean isChecked)191 public boolean onSwitchToggled(boolean isChecked) { 192 if (maybeEnforceRestrictions()) { 193 triggerParentPreferenceCallback(isChecked); 194 return true; 195 } 196 197 if (mIsSatelliteOn.get()) { 198 mContext.startActivity( 199 new Intent(mContext, SatelliteWarningDialogActivity.class) 200 .putExtra( 201 EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, 202 TYPE_IS_BLUETOOTH) 203 ); 204 mSwitchController.setChecked(!isChecked); 205 return false; 206 } 207 208 // Show toast message if Bluetooth is not allowed in airplane mode 209 if (isChecked && 210 !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) { 211 Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show(); 212 // Reset switch to off 213 mSwitchController.setChecked(false); 214 triggerParentPreferenceCallback(false); 215 return false; 216 } 217 218 mMetricsFeatureProvider.action(mContext, mMetricsEvent, isChecked); 219 220 if (mBluetoothAdapter != null) { 221 boolean status = setBluetoothEnabled(isChecked); 222 // If we cannot toggle it ON then reset the UI assets: 223 // a) The switch should be OFF but it should still be togglable (enabled = True) 224 // b) The switch bar should have OFF text. 225 if (isChecked && !status) { 226 mSwitchController.setChecked(false); 227 mSwitchController.setEnabled(true); 228 triggerParentPreferenceCallback(false); 229 return false; 230 } 231 } 232 mSwitchController.setEnabled(false); 233 triggerParentPreferenceCallback(isChecked); 234 return true; 235 } 236 237 /** 238 * Sets a callback back that this enabler will trigger in case the preference using the enabler 239 * still needed the callback on the SwitchController (which we now use). 240 * @param listener The listener with a callback to trigger. 241 */ setToggleCallback(SwitchWidgetController.OnSwitchChangeListener listener)242 public void setToggleCallback(SwitchWidgetController.OnSwitchChangeListener listener) { 243 mCallback = listener; 244 } 245 246 /** 247 * Enforces user restrictions disallowing Bluetooth (or its configuration) if there are any. 248 * 249 * @return if there was any user restriction to enforce. 250 */ 251 @VisibleForTesting maybeEnforceRestrictions()252 boolean maybeEnforceRestrictions() { 253 EnforcedAdmin admin = getEnforcedAdmin(mRestrictionUtils, mContext); 254 mSwitchController.setDisabledByAdmin(admin); 255 if (admin != null) { 256 mSwitchController.setChecked(false); 257 } 258 return admin != null; 259 } 260 getEnforcedAdmin(RestrictionUtils mRestrictionUtils, Context mContext)261 public static EnforcedAdmin getEnforcedAdmin(RestrictionUtils mRestrictionUtils, 262 Context mContext) { 263 EnforcedAdmin admin = mRestrictionUtils.checkIfRestrictionEnforced( 264 mContext, UserManager.DISALLOW_BLUETOOTH); 265 if (admin == null) { 266 admin = mRestrictionUtils.checkIfRestrictionEnforced( 267 mContext, UserManager.DISALLOW_CONFIG_BLUETOOTH); 268 } 269 return admin; 270 } 271 272 // This triggers the callback which was manually set for this enabler since the enabler will 273 // take over the switch controller callback triggerParentPreferenceCallback(boolean isChecked)274 private void triggerParentPreferenceCallback(boolean isChecked) { 275 if (mCallback != null) { 276 mCallback.onSwitchToggled(isChecked); 277 } 278 } 279 setBluetoothEnabled(boolean isEnabled)280 private boolean setBluetoothEnabled(boolean isEnabled) { 281 return isEnabled ? mBluetoothAdapter.enable() : mBluetoothAdapter.disable(); 282 } 283 } 284