1 /* 2 * Copyright (C) 2017 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.hfp; 18 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothHeadset; 21 import android.bluetooth.BluetoothSinkAudioPolicy; 22 import android.content.ActivityNotFoundException; 23 import android.content.Intent; 24 import android.media.AudioManager; 25 import android.net.Uri; 26 import android.os.PowerManager; 27 import android.telecom.PhoneAccount; 28 import android.telecom.PhoneAccountHandle; 29 import android.telecom.TelecomManager; 30 import android.telephony.TelephonyManager; 31 import android.util.Log; 32 33 import com.android.bluetooth.telephony.BluetoothInCallService; 34 import com.android.internal.annotations.VisibleForTesting; 35 36 import java.util.List; 37 38 /** 39 * Defines system calls that is used by state machine/service to either send or receive messages 40 * from the Android System. 41 */ 42 class HeadsetSystemInterface { 43 private static final String TAG = HeadsetSystemInterface.class.getSimpleName(); 44 45 private final HeadsetService mHeadsetService; 46 private final AudioManager mAudioManager; 47 private final HeadsetPhoneState mHeadsetPhoneState; 48 private final PowerManager.WakeLock mVoiceRecognitionWakeLock; 49 private final TelephonyManager mTelephonyManager; 50 private final TelecomManager mTelecomManager; 51 HeadsetSystemInterface(HeadsetService headsetService)52 HeadsetSystemInterface(HeadsetService headsetService) { 53 if (headsetService == null) { 54 Log.wtf(TAG, "HeadsetService parameter is null"); 55 } 56 mHeadsetService = headsetService; 57 mAudioManager = mHeadsetService.getSystemService(AudioManager.class); 58 PowerManager powerManager = mHeadsetService.getSystemService(PowerManager.class); 59 mVoiceRecognitionWakeLock = 60 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG + ":VoiceRecognition"); 61 mVoiceRecognitionWakeLock.setReferenceCounted(false); 62 mHeadsetPhoneState = new com.android.bluetooth.hfp.HeadsetPhoneState(mHeadsetService); 63 mTelephonyManager = mHeadsetService.getSystemService(TelephonyManager.class); 64 mTelecomManager = mHeadsetService.getSystemService(TelecomManager.class); 65 } 66 getBluetoothInCallServiceInstance()67 private static BluetoothInCallService getBluetoothInCallServiceInstance() { 68 return BluetoothInCallService.getInstance(); 69 } 70 71 /** Stop this system interface */ stop()72 public synchronized void stop() { 73 mHeadsetPhoneState.cleanup(); 74 } 75 76 /** 77 * Get audio manager. Most audio manager operations are pass through and therefore are not 78 * individually managed by this class 79 * 80 * @return audio manager for setting audio parameters 81 */ getAudioManager()82 public AudioManager getAudioManager() { 83 return mAudioManager; 84 } 85 86 /** 87 * Get wake lock for voice recognition 88 * 89 * @return wake lock for voice recognition 90 */ 91 @VisibleForTesting getVoiceRecognitionWakeLock()92 public PowerManager.WakeLock getVoiceRecognitionWakeLock() { 93 return mVoiceRecognitionWakeLock; 94 } 95 96 /** 97 * Get HeadsetPhoneState instance to interact with Telephony service 98 * 99 * @return HeadsetPhoneState interface to interact with Telephony service 100 */ 101 @VisibleForTesting getHeadsetPhoneState()102 public HeadsetPhoneState getHeadsetPhoneState() { 103 return mHeadsetPhoneState; 104 } 105 106 /** 107 * Answer the current incoming call in Telecom service 108 * 109 * @param device the Bluetooth device used for answering this call 110 */ 111 @VisibleForTesting answerCall(BluetoothDevice device)112 public void answerCall(BluetoothDevice device) { 113 Log.d(TAG, "answerCall"); 114 if (device == null) { 115 Log.w(TAG, "answerCall device is null"); 116 return; 117 } 118 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 119 if (bluetoothInCallService == null) { 120 Log.e(TAG, "Handsfree phone proxy null for answering call"); 121 return; 122 } 123 BluetoothSinkAudioPolicy callAudioPolicy = mHeadsetService.getHfpCallAudioPolicy(device); 124 if (callAudioPolicy == null 125 || callAudioPolicy.getCallEstablishPolicy() 126 != BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) { 127 mHeadsetService.setActiveDevice(device); 128 } 129 bluetoothInCallService.answerCall(); 130 } 131 132 /** 133 * Hangup the current call, could either be Telecom call or virtual call 134 * 135 * @param device the Bluetooth device used for hanging up this call 136 */ 137 @VisibleForTesting hangupCall(BluetoothDevice device)138 public void hangupCall(BluetoothDevice device) { 139 if (device == null) { 140 Log.w(TAG, "hangupCall device is null"); 141 return; 142 } 143 // Close the virtual call if active. Virtual call should be 144 // terminated for CHUP callback event 145 if (mHeadsetService.isVirtualCallStarted()) { 146 mHeadsetService.stopScoUsingVirtualVoiceCall(); 147 } else { 148 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 149 if (bluetoothInCallService != null) { 150 bluetoothInCallService.hangupCall(); 151 } else { 152 Log.e(TAG, "Handsfree phone proxy null for hanging up call"); 153 } 154 } 155 } 156 157 /** 158 * Instructs Telecom to play the specified DTMF tone for the current foreground call 159 * 160 * @param dtmf dtmf code 161 * @param device the Bluetooth device that sent this code 162 */ sendDtmf(int dtmf, BluetoothDevice device)163 boolean sendDtmf(int dtmf, BluetoothDevice device) { 164 if (device == null) { 165 Log.w(TAG, "sendDtmf device is null"); 166 return false; 167 } 168 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 169 if (bluetoothInCallService != null) { 170 return bluetoothInCallService.sendDtmf(dtmf); 171 } else { 172 Log.e(TAG, "Handsfree phone proxy null for sending DTMF"); 173 } 174 return false; 175 } 176 177 /** 178 * Instructs Telecom hold an incoming call 179 * 180 * @param chld index of the call to hold 181 */ 182 @VisibleForTesting processChld(HeadsetService headsetService, int chld)183 public boolean processChld(HeadsetService headsetService, int chld) { 184 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 185 if (bluetoothInCallService != null) { 186 return bluetoothInCallService.processChld(headsetService, chld); 187 } else { 188 Log.e(TAG, "Handsfree phone proxy null for sending DTMF"); 189 } 190 return false; 191 } 192 193 /** Check for HD codec for voice call */ 194 @VisibleForTesting isHighDefCallInProgress()195 public boolean isHighDefCallInProgress() { 196 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 197 if (bluetoothInCallService != null) { 198 return bluetoothInCallService.isHighDefCallInProgress(); 199 } else { 200 Log.e(TAG, "Handsfree phone proxy null"); 201 } 202 return false; 203 } 204 205 /** 206 * Get the the alphabetic name of current registered operator. 207 * 208 * @return null on error, empty string if not available 209 */ 210 @VisibleForTesting getNetworkOperator()211 public String getNetworkOperator() { 212 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 213 if (bluetoothInCallService == null) { 214 Log.e(TAG, "getNetworkOperator() failed: mBluetoothInCallService is null"); 215 return null; 216 } 217 // Should never return null 218 return bluetoothInCallService.getNetworkOperator(); 219 } 220 221 /** 222 * Get the phone number of this device without incall service 223 * 224 * @return empty if unavailable 225 */ getNumberWithoutInCallService()226 private String getNumberWithoutInCallService() { 227 PhoneAccount account = null; 228 String address = ""; 229 230 // Get the label for the default Phone Account. 231 List<PhoneAccountHandle> handles = 232 mTelecomManager.getPhoneAccountsSupportingScheme(PhoneAccount.SCHEME_TEL); 233 while (handles.iterator().hasNext()) { 234 account = mTelecomManager.getPhoneAccount(handles.iterator().next()); 235 break; 236 } 237 238 if (account != null) { 239 Uri addressUri = account.getAddress(); 240 241 if (addressUri != null) { 242 address = addressUri.getSchemeSpecificPart(); 243 } 244 } 245 246 if (address.isEmpty()) { 247 address = mTelephonyManager.getLine1Number(); 248 if (address == null) address = ""; 249 } 250 251 Log.i(TAG, "get phone number -> '" + address + "'"); 252 253 return address; 254 } 255 256 /** 257 * Get the phone number of this device 258 * 259 * @return null if unavailable 260 */ 261 @VisibleForTesting getSubscriberNumber()262 public String getSubscriberNumber() { 263 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 264 if (bluetoothInCallService == null) { 265 Log.e(TAG, "getSubscriberNumber() failed: mBluetoothInCallService is null"); 266 Log.i(TAG, "Try to get phone number without mBluetoothInCallService."); 267 return getNumberWithoutInCallService(); 268 } 269 return bluetoothInCallService.getSubscriberNumber(); 270 } 271 272 /** 273 * Ask the Telecom service to list current list of calls through CLCC response {@link 274 * BluetoothHeadset#clccResponse(int, int, int, int, boolean, String, int)} 275 */ 276 @VisibleForTesting listCurrentCalls(HeadsetService headsetService)277 public boolean listCurrentCalls(HeadsetService headsetService) { 278 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 279 if (bluetoothInCallService == null) { 280 Log.e(TAG, "listCurrentCalls() failed: mBluetoothInCallService is null"); 281 return false; 282 } 283 return bluetoothInCallService.listCurrentCalls(headsetService); 284 } 285 286 /** 287 * Request Telecom service to send an update of the current call state to the headset service 288 * through {@link BluetoothHeadset#phoneStateChanged(int, int, int, String, int)} 289 */ 290 @VisibleForTesting queryPhoneState(HeadsetService headsetService)291 public void queryPhoneState(HeadsetService headsetService) { 292 BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance(); 293 if (bluetoothInCallService != null) { 294 bluetoothInCallService.queryPhoneState(headsetService); 295 } else { 296 Log.e(TAG, "Handsfree phone proxy null for query phone state"); 297 } 298 } 299 300 /** 301 * Check if we are currently in a phone call 302 * 303 * @return True iff we are in a phone call 304 */ isInCall()305 boolean isInCall() { 306 return ((mHeadsetPhoneState.getNumActiveCall() > 0) 307 || (mHeadsetPhoneState.getNumHeldCall() > 0) 308 || ((mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE) 309 && (mHeadsetPhoneState.getCallState() 310 != HeadsetHalConstants.CALL_STATE_INCOMING))); 311 } 312 313 /** 314 * Check if there is currently an incoming call 315 * 316 * @return True iff there is an incoming call 317 */ 318 @VisibleForTesting isRinging()319 public boolean isRinging() { 320 return mHeadsetPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING; 321 } 322 323 /** 324 * Check if call status is idle 325 * 326 * @return true if call state is neither ringing nor in call 327 */ 328 @VisibleForTesting isCallIdle()329 public boolean isCallIdle() { 330 return !isInCall() && !isRinging(); 331 } 332 333 /** 334 * Activate voice recognition on Android system 335 * 336 * @return true if activation succeeds, caller should wait for {@link 337 * BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} callback that will then trigger 338 * {@link HeadsetService#startVoiceRecognition(BluetoothDevice)}, false if failed to 339 * activate 340 */ 341 @VisibleForTesting activateVoiceRecognition()342 public boolean activateVoiceRecognition() { 343 Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND); 344 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 345 try { 346 mHeadsetService.startActivity(intent); 347 } catch (ActivityNotFoundException e) { 348 Log.e(TAG, "activateVoiceRecognition, failed due to activity not found for " + intent); 349 return false; 350 } 351 return true; 352 } 353 354 /** 355 * Deactivate voice recognition on Android system 356 * 357 * @return true if activation succeeds, caller should wait for {@link 358 * BluetoothHeadset#stopVoiceRecognition(BluetoothDevice)} callback that will then trigger 359 * {@link HeadsetService#stopVoiceRecognition(BluetoothDevice)}, false if failed to activate 360 */ 361 @VisibleForTesting deactivateVoiceRecognition()362 public boolean deactivateVoiceRecognition() { 363 // TODO: need a method to deactivate voice recognition on Android 364 return true; 365 } 366 } 367