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