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