1 /* 2 * Copyright (C) 2019 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.bluetooth.btservice; 18 19 import static android.Manifest.permission.BLUETOOTH_CONNECT; 20 import static android.bluetooth.BluetoothProfile.STATE_CONNECTED; 21 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothProfile; 24 import android.content.Intent; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.os.Message; 28 import android.os.UserHandle; 29 import android.util.Log; 30 31 import com.android.bluetooth.Utils; 32 import com.android.bluetooth.a2dp.A2dpService; 33 import com.android.bluetooth.hfp.HeadsetService; 34 35 import java.util.ArrayList; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 40 /** 41 * The silence device manager controls silence mode for A2DP, HFP, and AVRCP. 42 * 43 * <p>1) If an active device (for A2DP or HFP) enters silence mode, the active device for that 44 * profile will be set to null. 2) If a device exits silence mode while the A2DP or HFP active 45 * device is null, the device will be set as the active device for that profile. 3) If a device is 46 * disconnected, it exits silence mode. 4) If a device is set as the active device for A2DP or HFP, 47 * while silence mode is enabled, then the device will exit silence mode. 5) If a device is in 48 * silence mode, AVRCP position change event and HFP AG indicators will be disabled. 6) If a device 49 * is not connected with A2DP or HFP, it cannot enter silence mode. 50 */ 51 public class SilenceDeviceManager { 52 private static final String TAG = SilenceDeviceManager.class.getSimpleName(); 53 54 private final AdapterService mAdapterService; 55 private final ServiceFactory mFactory; 56 private final Handler mHandler; 57 58 private final Map<BluetoothDevice, Boolean> mSilenceDevices = new HashMap<>(); 59 private final List<BluetoothDevice> mA2dpConnectedDevices = new ArrayList<>(); 60 private final List<BluetoothDevice> mHfpConnectedDevices = new ArrayList<>(); 61 62 private static final int MSG_SILENCE_DEVICE_STATE_CHANGED = 1; 63 private static final int MSG_A2DP_CONNECTION_STATE_CHANGED = 10; 64 private static final int MSG_HFP_CONNECTION_STATE_CHANGED = 11; 65 private static final int MSG_A2DP_ACTIVE_DEVICE_CHANGED = 20; 66 private static final int MSG_HFP_ACTIVE_DEVICE_CHANGED = 21; 67 private static final int ENABLE_SILENCE = 0; 68 private static final int DISABLE_SILENCE = 1; 69 70 /** 71 * Called when active state of audio profiles changed 72 * 73 * @param profile The Bluetooth profile of which active state changed 74 * @param device The device currently activated. {@code null} if no device is active 75 */ profileActiveDeviceChanged(int profile, BluetoothDevice device)76 public void profileActiveDeviceChanged(int profile, BluetoothDevice device) { 77 switch (profile) { 78 case BluetoothProfile.A2DP: 79 mHandler.obtainMessage(MSG_A2DP_ACTIVE_DEVICE_CHANGED, device).sendToTarget(); 80 break; 81 case BluetoothProfile.HEADSET: 82 mHandler.obtainMessage(MSG_HFP_ACTIVE_DEVICE_CHANGED, device).sendToTarget(); 83 break; 84 default: 85 break; 86 } 87 } 88 89 /** 90 * Called when A2DP connection state changed by A2dpService 91 * 92 * @param device The device of which connection state was changed 93 * @param fromState The previous connection state of the device 94 * @param toState The new connection state of the device 95 */ a2dpConnectionStateChanged(BluetoothDevice device, int fromState, int toState)96 public void a2dpConnectionStateChanged(BluetoothDevice device, int fromState, int toState) { 97 mHandler.obtainMessage(MSG_A2DP_CONNECTION_STATE_CHANGED, fromState, toState, device) 98 .sendToTarget(); 99 } 100 101 /** 102 * Called when HFP connection state changed by HeadsetService 103 * 104 * @param device The device of which connection state was changed 105 * @param fromState The previous connection state of the device 106 * @param toState The new connection state of the device 107 */ hfpConnectionStateChanged(BluetoothDevice device, int fromState, int toState)108 public void hfpConnectionStateChanged(BluetoothDevice device, int fromState, int toState) { 109 mHandler.obtainMessage(MSG_HFP_CONNECTION_STATE_CHANGED, fromState, toState, device) 110 .sendToTarget(); 111 } 112 113 class SilenceDeviceManagerHandler extends Handler { 114 SilenceDeviceManagerHandler(Looper looper)115 SilenceDeviceManagerHandler(Looper looper) { 116 super(looper); 117 } 118 119 @Override handleMessage(Message msg)120 public void handleMessage(Message msg) { 121 Log.d(TAG, "handleMessage: " + msg.what); 122 switch (msg.what) { 123 case MSG_SILENCE_DEVICE_STATE_CHANGED: 124 { 125 BluetoothDevice device = (BluetoothDevice) msg.obj; 126 boolean state = (msg.arg1 == ENABLE_SILENCE); 127 handleSilenceDeviceStateChanged(device, state); 128 } 129 break; 130 131 case MSG_A2DP_CONNECTION_STATE_CHANGED: 132 BluetoothDevice device = (BluetoothDevice) msg.obj; 133 int prevState = msg.arg1; 134 int nextState = msg.arg2; 135 136 if (nextState == STATE_CONNECTED) { 137 // enter connected state 138 addConnectedDevice(device, BluetoothProfile.A2DP); 139 if (!mSilenceDevices.containsKey(device)) { 140 mSilenceDevices.put(device, false); 141 } 142 } else if (prevState == STATE_CONNECTED) { 143 // exiting from connected state 144 removeConnectedDevice(device, BluetoothProfile.A2DP); 145 if (!isBluetoothAudioConnected(device)) { 146 handleSilenceDeviceStateChanged(device, false); 147 mSilenceDevices.remove(device); 148 } 149 } 150 break; 151 152 case MSG_HFP_CONNECTION_STATE_CHANGED: 153 BluetoothDevice bluetoothDevice = (BluetoothDevice) msg.obj; 154 int prev = msg.arg1; 155 int next = msg.arg2; 156 157 if (next == STATE_CONNECTED) { 158 // enter connected state 159 addConnectedDevice(bluetoothDevice, BluetoothProfile.HEADSET); 160 if (!mSilenceDevices.containsKey(bluetoothDevice)) { 161 mSilenceDevices.put(bluetoothDevice, false); 162 } 163 } else if (prev == STATE_CONNECTED) { 164 // exiting from connected state 165 removeConnectedDevice(bluetoothDevice, BluetoothProfile.HEADSET); 166 if (!isBluetoothAudioConnected(bluetoothDevice)) { 167 handleSilenceDeviceStateChanged(bluetoothDevice, false); 168 mSilenceDevices.remove(bluetoothDevice); 169 } 170 } 171 break; 172 173 case MSG_A2DP_ACTIVE_DEVICE_CHANGED: 174 BluetoothDevice a2dpActiveDevice = (BluetoothDevice) msg.obj; 175 if (getSilenceMode(a2dpActiveDevice)) { 176 // Resume the device from silence mode. 177 setSilenceMode(a2dpActiveDevice, false); 178 } 179 break; 180 181 case MSG_HFP_ACTIVE_DEVICE_CHANGED: 182 BluetoothDevice hfpActiveDevice = (BluetoothDevice) msg.obj; 183 if (getSilenceMode(hfpActiveDevice)) { 184 // Resume the device from silence mode. 185 setSilenceMode(hfpActiveDevice, false); 186 } 187 break; 188 189 default: 190 Log.e(TAG, "Unknown message: " + msg.what); 191 break; 192 } 193 } 194 } 195 SilenceDeviceManager(AdapterService service, ServiceFactory factory, Looper looper)196 SilenceDeviceManager(AdapterService service, ServiceFactory factory, Looper looper) { 197 mAdapterService = service; 198 mFactory = factory; 199 mHandler = new SilenceDeviceManagerHandler(looper); 200 } 201 cleanup()202 void cleanup() { 203 Log.v(TAG, "cleanup()"); 204 mSilenceDevices.clear(); 205 } 206 setSilenceMode(BluetoothDevice device, boolean silence)207 boolean setSilenceMode(BluetoothDevice device, boolean silence) { 208 Log.d(TAG, "setSilenceMode: " + device + ", " + silence); 209 mHandler.obtainMessage( 210 MSG_SILENCE_DEVICE_STATE_CHANGED, 211 silence ? ENABLE_SILENCE : DISABLE_SILENCE, 212 0, 213 device) 214 .sendToTarget(); 215 return true; 216 } 217 handleSilenceDeviceStateChanged(BluetoothDevice device, boolean state)218 void handleSilenceDeviceStateChanged(BluetoothDevice device, boolean state) { 219 boolean oldState = getSilenceMode(device); 220 if (oldState == state) { 221 return; 222 } 223 if (!isBluetoothAudioConnected(device)) { 224 if (oldState) { 225 // Device is disconnected, resume all silenced profiles. 226 state = false; 227 } else { 228 Log.d(TAG, "Device is not connected to any Bluetooth audio."); 229 return; 230 } 231 } 232 mSilenceDevices.replace(device, state); 233 234 A2dpService a2dpService = mFactory.getA2dpService(); 235 if (a2dpService != null) { 236 a2dpService.setSilenceMode(device, state); 237 } 238 HeadsetService headsetService = mFactory.getHeadsetService(); 239 if (headsetService != null) { 240 headsetService.setSilenceMode(device, state); 241 } 242 Log.i(TAG, "Silence mode change " + device + ": " + oldState + " -> " + state); 243 broadcastSilenceStateChange(device); 244 } 245 broadcastSilenceStateChange(BluetoothDevice device)246 private void broadcastSilenceStateChange(BluetoothDevice device) { 247 Intent intent = new Intent(BluetoothDevice.ACTION_SILENCE_MODE_CHANGED); 248 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 249 mAdapterService.sendBroadcastAsUser( 250 intent, 251 UserHandle.ALL, 252 BLUETOOTH_CONNECT, 253 Utils.getTempBroadcastOptions().toBundle()); 254 } 255 getSilenceMode(BluetoothDevice device)256 boolean getSilenceMode(BluetoothDevice device) { 257 boolean state = false; 258 if (mSilenceDevices.containsKey(device)) { 259 state = mSilenceDevices.get(device); 260 } 261 return state; 262 } 263 addConnectedDevice(BluetoothDevice device, int profile)264 private void addConnectedDevice(BluetoothDevice device, int profile) { 265 Log.d( 266 TAG, 267 "addConnectedDevice: " 268 + device 269 + ", profile:" 270 + BluetoothProfile.getProfileName(profile)); 271 switch (profile) { 272 case BluetoothProfile.A2DP: 273 if (!mA2dpConnectedDevices.contains(device)) { 274 mA2dpConnectedDevices.add(device); 275 } 276 break; 277 case BluetoothProfile.HEADSET: 278 if (!mHfpConnectedDevices.contains(device)) { 279 mHfpConnectedDevices.add(device); 280 } 281 break; 282 } 283 } 284 removeConnectedDevice(BluetoothDevice device, int profile)285 private void removeConnectedDevice(BluetoothDevice device, int profile) { 286 Log.d( 287 TAG, 288 "removeConnectedDevice: " 289 + device 290 + ", profile:" 291 + BluetoothProfile.getProfileName(profile)); 292 switch (profile) { 293 case BluetoothProfile.A2DP: 294 if (mA2dpConnectedDevices.contains(device)) { 295 mA2dpConnectedDevices.remove(device); 296 } 297 break; 298 case BluetoothProfile.HEADSET: 299 if (mHfpConnectedDevices.contains(device)) { 300 mHfpConnectedDevices.remove(device); 301 } 302 break; 303 } 304 } 305 isBluetoothAudioConnected(BluetoothDevice device)306 private boolean isBluetoothAudioConnected(BluetoothDevice device) { 307 return (mA2dpConnectedDevices.contains(device) || mHfpConnectedDevices.contains(device)); 308 } 309 dump(StringBuilder sb)310 protected void dump(StringBuilder sb) { 311 sb.append("SilenceDeviceManager:\n"); 312 sb.append(" Address | Is silenced?\n"); 313 for (BluetoothDevice device : mSilenceDevices.keySet()) { 314 sb.append(" ") 315 .append(device) 316 .append(" | ") 317 .append(getSilenceMode(device)) 318 .append("\n"); 319 } 320 } 321 } 322