1 /* 2 * Copyright (C) 2014 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.server.telecom; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothHeadset; 22 import android.bluetooth.BluetoothProfile; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.os.SystemClock; 28 29 import com.android.internal.util.IndentingPrintWriter; 30 31 import java.util.List; 32 33 /** 34 * Listens to and caches bluetooth headset state. Used By the CallAudioManager for maintaining 35 * overall audio state. Also provides method for connecting the bluetooth headset to the phone call. 36 */ 37 public class BluetoothManager { 38 39 private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = 40 new BluetoothProfile.ServiceListener() { 41 @Override 42 public void onServiceConnected(int profile, BluetoothProfile proxy) { 43 mBluetoothHeadset = (BluetoothHeadset) proxy; 44 Log.v(this, "- Got BluetoothHeadset: " + mBluetoothHeadset); 45 } 46 47 @Override 48 public void onServiceDisconnected(int profile) { 49 mBluetoothHeadset = null; 50 } 51 }; 52 53 /** 54 * Receiver for misc intent broadcasts the BluetoothManager cares about. 55 */ 56 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 57 @Override 58 public void onReceive(Context context, Intent intent) { 59 String action = intent.getAction(); 60 61 if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { 62 int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 63 BluetoothHeadset.STATE_DISCONNECTED); 64 Log.d(this, "mReceiver: HEADSET_STATE_CHANGED_ACTION"); 65 Log.d(this, "==> new state: %s ", bluetoothHeadsetState); 66 updateBluetoothState(); 67 } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { 68 int bluetoothHeadsetAudioState = 69 intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 70 BluetoothHeadset.STATE_AUDIO_DISCONNECTED); 71 Log.d(this, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION"); 72 Log.d(this, "==> new state: %s", bluetoothHeadsetAudioState); 73 updateBluetoothState(); 74 } 75 } 76 }; 77 78 private final BluetoothAdapter mBluetoothAdapter; 79 private final CallAudioManager mCallAudioManager; 80 81 private BluetoothHeadset mBluetoothHeadset; 82 private boolean mBluetoothConnectionPending = false; 83 private long mBluetoothConnectionRequestTime; 84 85 BluetoothManager(Context context, CallAudioManager callAudioManager)86 public BluetoothManager(Context context, CallAudioManager callAudioManager) { 87 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 88 mCallAudioManager = callAudioManager; 89 90 if (mBluetoothAdapter != null) { 91 mBluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener, 92 BluetoothProfile.HEADSET); 93 } 94 95 // Register for misc other intent broadcasts. 96 IntentFilter intentFilter = 97 new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 98 intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); 99 context.registerReceiver(mReceiver, intentFilter); 100 } 101 102 // 103 // Bluetooth helper methods. 104 // 105 // - BluetoothAdapter is the Bluetooth system service. If 106 // getDefaultAdapter() returns null 107 // then the device is not BT capable. Use BluetoothDevice.isEnabled() 108 // to see if BT is enabled on the device. 109 // 110 // - BluetoothHeadset is the API for the control connection to a 111 // Bluetooth Headset. This lets you completely connect/disconnect a 112 // headset (which we don't do from the Phone UI!) but also lets you 113 // get the address of the currently active headset and see whether 114 // it's currently connected. 115 116 /** 117 * @return true if the Bluetooth on/off switch in the UI should be 118 * available to the user (i.e. if the device is BT-capable 119 * and a headset is connected.) 120 */ isBluetoothAvailable()121 boolean isBluetoothAvailable() { 122 Log.v(this, "isBluetoothAvailable()..."); 123 124 // There's no need to ask the Bluetooth system service if BT is enabled: 125 // 126 // BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 127 // if ((adapter == null) || !adapter.isEnabled()) { 128 // Log.d(this, " ==> FALSE (BT not enabled)"); 129 // return false; 130 // } 131 // Log.d(this, " - BT enabled! device name " + adapter.getName() 132 // + ", address " + adapter.getAddress()); 133 // 134 // ...since we already have a BluetoothHeadset instance. We can just 135 // call isConnected() on that, and assume it'll be false if BT isn't 136 // enabled at all. 137 138 // Check if there's a connected headset, using the BluetoothHeadset API. 139 boolean isConnected = false; 140 if (mBluetoothHeadset != null) { 141 List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices(); 142 143 if (deviceList.size() > 0) { 144 isConnected = true; 145 for (int i = 0; i < deviceList.size(); i++) { 146 BluetoothDevice device = deviceList.get(i); 147 Log.v(this, "state = " + mBluetoothHeadset.getConnectionState(device) 148 + "for headset: " + device); 149 } 150 } 151 } 152 153 Log.v(this, " ==> " + isConnected); 154 return isConnected; 155 } 156 157 /** 158 * @return true if a BT Headset is available, and its audio is currently connected. 159 */ isBluetoothAudioConnected()160 boolean isBluetoothAudioConnected() { 161 if (mBluetoothHeadset == null) { 162 Log.v(this, "isBluetoothAudioConnected: ==> FALSE (null mBluetoothHeadset)"); 163 return false; 164 } 165 List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices(); 166 167 if (deviceList.isEmpty()) { 168 return false; 169 } 170 for (int i = 0; i < deviceList.size(); i++) { 171 BluetoothDevice device = deviceList.get(i); 172 boolean isAudioOn = mBluetoothHeadset.isAudioConnected(device); 173 Log.v(this, "isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn 174 + "for headset: " + device); 175 if (isAudioOn) { 176 return true; 177 } 178 } 179 return false; 180 } 181 182 /** 183 * Helper method used to control the onscreen "Bluetooth" indication; 184 * 185 * @return true if a BT device is available and its audio is currently connected, 186 * <b>or</b> if we issued a BluetoothHeadset.connectAudio() 187 * call within the last 5 seconds (which presumably means 188 * that the BT audio connection is currently being set 189 * up, and will be connected soon.) 190 */ isBluetoothAudioConnectedOrPending()191 /* package */ boolean isBluetoothAudioConnectedOrPending() { 192 if (isBluetoothAudioConnected()) { 193 Log.v(this, "isBluetoothAudioConnectedOrPending: ==> TRUE (really connected)"); 194 return true; 195 } 196 197 // If we issued a connectAudio() call "recently enough", even 198 // if BT isn't actually connected yet, let's still pretend BT is 199 // on. This makes the onscreen indication more responsive. 200 if (mBluetoothConnectionPending) { 201 long timeSinceRequest = 202 SystemClock.elapsedRealtime() - mBluetoothConnectionRequestTime; 203 if (timeSinceRequest < 5000 /* 5 seconds */) { 204 Log.v(this, "isBluetoothAudioConnectedOrPending: ==> TRUE (requested " 205 + timeSinceRequest + " msec ago)"); 206 return true; 207 } else { 208 Log.v(this, "isBluetoothAudioConnectedOrPending: ==> FALSE (request too old: " 209 + timeSinceRequest + " msec ago)"); 210 mBluetoothConnectionPending = false; 211 return false; 212 } 213 } 214 215 Log.v(this, "isBluetoothAudioConnectedOrPending: ==> FALSE"); 216 return false; 217 } 218 219 /** 220 * Notified audio manager of a change to the bluetooth state. 221 */ updateBluetoothState()222 void updateBluetoothState() { 223 mCallAudioManager.onBluetoothStateChange(this); 224 } 225 connectBluetoothAudio()226 void connectBluetoothAudio() { 227 Log.v(this, "connectBluetoothAudio()..."); 228 if (mBluetoothHeadset != null) { 229 mBluetoothHeadset.connectAudio(); 230 } 231 232 // Watch out: The bluetooth connection doesn't happen instantly; 233 // the connectAudio() call returns instantly but does its real 234 // work in another thread. The mBluetoothConnectionPending flag 235 // is just a little trickery to ensure that the onscreen UI updates 236 // instantly. (See isBluetoothAudioConnectedOrPending() above.) 237 mBluetoothConnectionPending = true; 238 mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime(); 239 } 240 disconnectBluetoothAudio()241 void disconnectBluetoothAudio() { 242 Log.v(this, "disconnectBluetoothAudio()..."); 243 if (mBluetoothHeadset != null) { 244 mBluetoothHeadset.disconnectAudio(); 245 } 246 mBluetoothConnectionPending = false; 247 } 248 249 /** 250 * Dumps the state of the {@link BluetoothManager}. 251 * 252 * @param pw The {@code IndentingPrintWriter} to write the state to. 253 */ dump(IndentingPrintWriter pw)254 public void dump(IndentingPrintWriter pw) { 255 pw.println("isBluetoothAvailable: " + isBluetoothAvailable()); 256 pw.println("isBluetoothAudioConnected: " + isBluetoothAudioConnected()); 257 pw.println("isBluetoothAudioConnectedOrPending: " + isBluetoothAudioConnectedOrPending()); 258 259 if (mBluetoothAdapter != null) { 260 if (mBluetoothHeadset != null) { 261 List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices(); 262 263 if (deviceList.size() > 0) { 264 BluetoothDevice device = deviceList.get(0); 265 pw.println("BluetoothHeadset.getCurrentDevice: " + device); 266 pw.println("BluetoothHeadset.State: " 267 + mBluetoothHeadset.getConnectionState(device)); 268 pw.println("BluetoothHeadset audio connected: " + 269 mBluetoothHeadset.isAudioConnected(device)); 270 } 271 } else { 272 pw.println("mBluetoothHeadset is null"); 273 } 274 } else { 275 pw.println("mBluetoothAdapter is null; device is not BT capable"); 276 } 277 } 278 } 279