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