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 mSwitchController.setTitle(context.getString(R.string.bluetooth_main_switch_title)); 79 80 mValidListener = false; 81 mMetricsEvent = metricsEvent; 82 83 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 84 if (mBluetoothAdapter == null) { 85 // Bluetooth is not supported 86 mSwitchController.setEnabled(false); 87 } 88 mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 89 mRestrictionUtils = restrictionUtils; 90 } 91 setupSwitchController()92 public void setupSwitchController() { 93 mSwitchController.setupView(); 94 } 95 teardownSwitchController()96 public void teardownSwitchController() { 97 mSwitchController.teardownView(); 98 } 99 resume(Context context)100 public void resume(Context context) { 101 if (mContext != context) { 102 mContext = context; 103 } 104 105 final boolean restricted = maybeEnforceRestrictions(); 106 107 if (mBluetoothAdapter == null) { 108 mSwitchController.setEnabled(false); 109 return; 110 } 111 112 // Bluetooth state is not sticky, so set it manually 113 if (!restricted) { 114 handleStateChanged(mBluetoothAdapter.getState()); 115 } 116 117 mSwitchController.startListening(); 118 mContext.registerReceiver(mReceiver, mIntentFilter); 119 mValidListener = true; 120 } 121 pause()122 public void pause() { 123 if (mBluetoothAdapter == null) { 124 return; 125 } 126 if (mValidListener) { 127 mSwitchController.stopListening(); 128 mContext.unregisterReceiver(mReceiver); 129 mValidListener = false; 130 } 131 } 132 handleStateChanged(int state)133 void handleStateChanged(int state) { 134 switch (state) { 135 case BluetoothAdapter.STATE_TURNING_ON: 136 mSwitchController.setEnabled(false); 137 break; 138 case BluetoothAdapter.STATE_ON: 139 setChecked(true); 140 mSwitchController.setEnabled(true); 141 break; 142 case BluetoothAdapter.STATE_TURNING_OFF: 143 mSwitchController.setEnabled(false); 144 break; 145 case BluetoothAdapter.STATE_OFF: 146 setChecked(false); 147 mSwitchController.setEnabled(true); 148 break; 149 default: 150 setChecked(false); 151 mSwitchController.setEnabled(true); 152 } 153 } 154 setChecked(boolean isChecked)155 private void setChecked(boolean isChecked) { 156 if (isChecked != mSwitchController.isChecked()) { 157 // set listener to null, so onCheckedChanged won't be called 158 // if the checked status on Switch isn't changed by user click 159 if (mValidListener) { 160 mSwitchController.stopListening(); 161 } 162 mSwitchController.setChecked(isChecked); 163 if (mValidListener) { 164 mSwitchController.startListening(); 165 } 166 } 167 } 168 169 @Override onSwitchToggled(boolean isChecked)170 public boolean onSwitchToggled(boolean isChecked) { 171 if (maybeEnforceRestrictions()) { 172 triggerParentPreferenceCallback(isChecked); 173 return true; 174 } 175 176 // Show toast message if Bluetooth is not allowed in airplane mode 177 if (isChecked && 178 !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) { 179 Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show(); 180 // Reset switch to off 181 mSwitchController.setChecked(false); 182 triggerParentPreferenceCallback(false); 183 return false; 184 } 185 186 mMetricsFeatureProvider.action(mContext, mMetricsEvent, isChecked); 187 188 if (mBluetoothAdapter != null) { 189 boolean status = setBluetoothEnabled(isChecked); 190 // If we cannot toggle it ON then reset the UI assets: 191 // a) The switch should be OFF but it should still be togglable (enabled = True) 192 // b) The switch bar should have OFF text. 193 if (isChecked && !status) { 194 mSwitchController.setChecked(false); 195 mSwitchController.setEnabled(true); 196 triggerParentPreferenceCallback(false); 197 return false; 198 } 199 } 200 mSwitchController.setEnabled(false); 201 triggerParentPreferenceCallback(isChecked); 202 return true; 203 } 204 205 /** 206 * Sets a callback back that this enabler will trigger in case the preference using the enabler 207 * still needed the callback on the SwitchController (which we now use). 208 * @param listener The listener with a callback to trigger. 209 */ setToggleCallback(SwitchWidgetController.OnSwitchChangeListener listener)210 public void setToggleCallback(SwitchWidgetController.OnSwitchChangeListener listener) { 211 mCallback = listener; 212 } 213 214 /** 215 * Enforces user restrictions disallowing Bluetooth (or its configuration) if there are any. 216 * 217 * @return if there was any user restriction to enforce. 218 */ 219 @VisibleForTesting maybeEnforceRestrictions()220 boolean maybeEnforceRestrictions() { 221 EnforcedAdmin admin = getEnforcedAdmin(mRestrictionUtils, mContext); 222 mSwitchController.setDisabledByAdmin(admin); 223 if (admin != null) { 224 mSwitchController.setChecked(false); 225 mSwitchController.setEnabled(false); 226 } 227 return admin != null; 228 } 229 getEnforcedAdmin(RestrictionUtils mRestrictionUtils, Context mContext)230 public static EnforcedAdmin getEnforcedAdmin(RestrictionUtils mRestrictionUtils, 231 Context mContext) { 232 EnforcedAdmin admin = mRestrictionUtils.checkIfRestrictionEnforced( 233 mContext, UserManager.DISALLOW_BLUETOOTH); 234 if (admin == null) { 235 admin = mRestrictionUtils.checkIfRestrictionEnforced( 236 mContext, UserManager.DISALLOW_CONFIG_BLUETOOTH); 237 } 238 return admin; 239 } 240 241 // This triggers the callback which was manually set for this enabler since the enabler will 242 // take over the switch controller callback triggerParentPreferenceCallback(boolean isChecked)243 private void triggerParentPreferenceCallback(boolean isChecked) { 244 if (mCallback != null) { 245 mCallback.onSwitchToggled(isChecked); 246 } 247 } 248 setBluetoothEnabled(boolean isEnabled)249 private boolean setBluetoothEnabled(boolean isEnabled) { 250 return isEnabled ? mBluetoothAdapter.enable() : mBluetoothAdapter.disable(); 251 } 252 } 253