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